Agent skill

Testing Flows With Turbine

by skydoves in skydoves/android-testing-skills

A skill your agent uses to assert Flow emissions in tests with Cash App Turbine.

Apache-2.0Auto-check passedMobile

Install Testing Flows With Turbine

skills CLI
$ npx skills add skydoves/android-testing-skills --skill testing-flows-with-turbine -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install skydoves/android-testing-skills testing-flows-with-turbine --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/jvm-tests/coroutines/testing-flows-with-turbine .claude/skills/testing-flows-with-turbine && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
testing-flows-with-turbine
GitHub stars
333
Token cost
~5.5k tokens
SKILL.md length
1,538 words
Files
1
Skills in repo
50
Repo updated
First seen
Licence
Apache-2.0

At a glance

A skill your agent uses to assert Flow emissions in tests with Cash App Turbine.

  • Works in 3 steps: test { } launches a new coroutine that… → The 3-second default timeout is wall… → After validate { } returns, Turbine…
  • Assert Flow emissions in tests with Cash App Turbine
  • SKILL.md covers When to use this skill, When NOT to use this skill, Prerequisites and flow.test signature, plus 10 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Testing Flows With Turbine is an agent skill from skydoves/android-testing-skills. Use this skill to assert Flow emissions in tests with Cash App Turbine. Covers flow.test entry, ReceiveTurbine API (awaitItem, awaitComplete, awaitError, expectMostRecentItem, skipItems, expectNoEvents, cancel and friends, plus the non-suspending takeItem/takeComplete/takeError), turbineScope/testIn for multi-flow tests, the standalone Turbine<T channel (add/close/asChannel/ensureAllEventsConsumed) for verifying non-Flow callbacks and listeners, the hot vs cold cancellation contract (StateFlow/SharedFlow never…

Its SKILL.md is about 5.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Mobile. The repository describes itself as: ⚡️ A set of skills for Android testing: Compose UI, AndroidX Test, JVM unit tests, and ADB. The licence is Apache-2.0.

When your agent uses it

  • Assert Flow emissions in tests with Cash App Turbine

Example prompts

  • “test hangs on collect”
  • “SharedFlow never completes”
  • “StateFlow first emission”
  • “/testing-flows-with-turbine”

Workflow steps

3 steps, taken from the first numbered list in SKILL.md.

  1. test { } launches a new coroutine that calls flow.collect { } and feeds every emission into a ReceiveTurbine channel. The collector is…
  2. The 3-second default timeout is wall clock — it is NOT driven by TestCoroutineScheduler and does NOT honor runTest's virtual time…
  3. After validate { } returns, Turbine asserts no unconsumed events remain. If any item / completion / error is pending, it throws…

What it can do on your machine

Read from SKILL.md and the folder at commit 8665ed5. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are kotlin).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • github.com
    • developer.android.com
    • kotlinlang.org

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Testing Flows With Turbine loads about 5.5k tokens when it runs. Until then it costs about 255 tokens; SKILL.md has 1,538 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~255
When it runs · the whole SKILL.md, loaded when a task matches
~5.5k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from skydoves/android-testing-skills at commit 8665ed5, republished under its Apache-2.0 licence (© skydoves). 1,538 words, ~5,518 tokens.

Download SKILL.mdSave it as .claude/skills/testing-flows-with-turbine/SKILL.md (or your agent's skills folder).
name
testing-flows-with-turbine
description
Use this skill to assert Flow emissions in tests with Cash App Turbine. Covers flow.test entry, ReceiveTurbine API (awaitItem, awaitComplete, awaitError, expectMostRecentItem, skipItems, expectNoEvents, cancel and friends, plus the non-suspending takeItem/takeComplete/takeError), turbineScope/testIn for multi-flow tests, the standalone Turbine<T> channel (add/close/asChannel/ensureAllEventsConsumed) for verifying non-Flow callbacks and listeners, the hot vs cold cancellation contract (StateFlow/SharedFlow never auto-complete), Turbine's wall-clock timeout decoupled from runTest virtual time, and the decision matrix between Turbine and raw flow.toList. If the user mentions Turbine, awaitItem, awaitComplete, expectMostRecentItem, takeItem, "test hangs on collect", "SharedFlow never completes", "StateFlow first emission", "TurbineAssertionError", "withTurbineTimeout", "Turbine() channel", "capture a callback in a test", or asserts a ViewModel's StateFlow/SharedFlow, use this skill.
license
Apache-2.0. See LICENSE for complete terms.
metadata.author
Jaewoong Eum (skydoves)
metadata.keywords
kotlin-coroutines, flow-testing, turbine, state-flow, shared-flow, await-item, await-complete, hot-flow, cold-flow, cash-app-turbine

Testing Flows With Turbine — Assert Emissions Without toList Hangs

Turbine is the canonical way to assert Flow emissions one-at-a-time, with built-in cancellation that prevents hot flows (StateFlow, SharedFlow) from hanging the test. This skill covers flow.test, the ReceiveTurbine API, turbineScope for multi-flow tests, and when raw flow.toList() actually beats Turbine. The non-Flow coroutine plumbing is ../testing-coroutines-with-runtest/SKILL.md.

When to use this skill

  • The class under test exposes a StateFlow<UiState> or SharedFlow<Event> and the test must assert each emission.
  • A test hangs on flow.toList() over a SharedFlow or StateFlow — neither completes.
  • The developer wants to assert "no more emissions for a beat" or "the next event is an error", which toList() cannot express.
  • Two flows must be observed concurrently (e.g. uiState plus a one-shot events flow) and asserted in interleaved order.
  • The build error is TurbineAssertionError: Unconsumed events found or Expected an item but found Complete/Error.
  • The developer mentions Turbine APIs: awaitItem, awaitComplete, awaitError, expectMostRecentItem, skipItems, expectNoEvents, withTurbineTimeout, turbineScope, testIn.

When NOT to use this skill

  • The test is about runTest, TestScope, Dispatchers.setMain, or virtual time. Use ../testing-coroutines-with-runtest/SKILL.md first; Turbine sits on top of runTest.
  • The flow is cold and completes deterministically, and the assertion is "the full list equals X". flow.toList() is shorter — see Decision matrix below.
  • The "flow" is actually Compose snapshot state. Read it via the test rule, not Turbine.

Prerequisites

  • Gradle: testImplementation("app.cash.turbine:turbine:1.x") (docs/CORPUS.md §G.1; R5).
  • Turbine multiplatform — JVM, Android, iOS, JS, Native, wasm. Apache 2.0, Square / Cash App.
  • The surrounding test must be a runTest { … } body (or any suspend context). flow.test { } is itself suspend.
  • For multi-flow tests, @OptIn(ExperimentalCoroutinesApi::class) because turbineScope interacts with TestScope.backgroundScope.

flow.test signature

kotlin
public suspend fun <T> Flow<T>.test(
    timeout: Duration = 3.seconds,
    name: String? = null,
    validate: suspend ReceiveTurbine<T>.() -> Unit,
)

(docs/CORPUS.md §G; R5.) Three things to internalize:

  1. test { } launches a new coroutine that calls flow.collect { } and feeds every emission into a ReceiveTurbine<T> channel. The collector is attached before validate { } runs, so it is safe to emit into a hot flow from inside the block without dropping the first item.
  2. The 3-second default timeout is wall clock — it is NOT driven by TestCoroutineScheduler and does NOT honor runTest's virtual time. awaitItem() waits up to 3 real seconds.
  3. After validate { } returns, Turbine asserts no unconsumed events remain. If any item / completion / error is pending, it throws TurbineAssertionError. The collecting coroutine is then cancelled, so cold flows do not leak.

The name argument is appended to error messages — invaluable when multiple turbines run in one test.

ReceiveTurbine API

kotlin
public interface ReceiveTurbine<T> {
    // suspending
    public suspend fun awaitItem(): T
    public suspend fun awaitComplete()
    public suspend fun awaitError(): Throwable
    public suspend fun awaitEvent(): Event<T>
    public suspend fun skipItems(count: Int)
    public suspend fun cancel()
    public suspend fun cancelAndIgnoreRemainingEvents()
    public suspend fun cancelAndConsumeRemainingEvents(): List<Event<T>>

    // non-suspending
    public fun expectNoEvents()
    public fun expectMostRecentItem(): T
    public fun ensureAllEventsConsumed()
}

(R5.)

CallUse when
awaitItem(): TNext event must be an item; fails on Complete/Error.
awaitComplete()Next event must be flow completion; fails on item/error.
awaitError(): ThrowableNext event must be an error; returns the throwable for further assertions.
awaitEvent(): Event<T>Sealed-class entry: Event.Item(value), Event.Complete, Event.Error(throwable).
skipItems(n)Drop n item events; fails if anything but an item is next. Useful to skip a StateFlow's seed.
expectMostRecentItem()Non-suspending; drains buffered items and returns the latest. The "final state" assertion for StateFlow-driven UIs where intermediate values are lossy.
expectNoEvents()Non-suspending; throws if anything is buffered. Pair with runCurrent()/advanceTimeBy(...) to assert no spurious emissions.
cancel()Stop the collector; any remaining events become an assertion failure on scope exit.
cancelAndIgnoreRemainingEvents()Stop, drain silently. The default tail of any hot-flow test.
cancelAndConsumeRemainingEvents()Returns List<Event<T>> for ad-hoc inspection.

Hot vs cold flow contract

Flow typeCompletes naturally?First awaitItem() returnsNeed cancel()?
flowOf(...) / flow { } (cold)yesfirst emissionno — awaitComplete() terminates
MutableSharedFlow<T> (replay = 0)nonext emission (or hangs)always
MutableStateFlow<T>noseed value (replay = 1)always

(docs/CORPUS.md §G; R5.)

CRITICAL: SharedFlow and StateFlow never call onCompletion. awaitComplete() on either will hang to the 3-second wall-clock timeout. Always end with cancel() (or cancelAndIgnoreRemainingEvents()).

StateFlow.test always emits the seed value first because StateFlow has replay = 1. If you do not care about the seed, skipItems(1) it.

turbineScope and testIn — multiple flows in one test

kotlin
public suspend fun <R> turbineScope(
    timeout: Duration? = null,
    validate: suspend TurbineTestContext.() -> R,
): R

public fun <T> Flow<T>.testIn(
    scope: CoroutineScope,
    timeout: Duration? = null,
    name: String? = null,
): ReceiveTurbine<T>

Use these when assertions are interleaved between two flows:

kotlin
runTest(mainRule.dispatcher) {
    turbineScope {
        val items = repo.items.testIn(backgroundScope, name = "items")
        val errors = repo.errors.testIn(backgroundScope, name = "errors")
        assertEquals(0, items.awaitItem())
        assertEquals(emptyList<Throwable>(), errors.expectMostRecentItem())
        items.cancel()
        errors.cancel()
    }
}

testIn requires explicit cancel() (or awaitComplete() / awaitError()) before the turbineScope ends; otherwise the scope assertion throws.

Standalone Turbine<T> — capturing callbacks and one-shot signals

Turbine is not only a Flow adapter. The standalone Turbine<T> is a send-and-receive channel with the same ReceiveTurbine assertion surface — for when the thing under test is not a Flow: a (T) -> Unit callback, a listener interface, a Channel you own, a hand-rolled fake that needs to record invocations.

kotlin
public fun <T> Turbine(timeout: Duration? = null, name: String? = null): Turbine<T>

public interface Turbine<T> : ReceiveTurbine<T> {
    public val isClosed: Boolean
    public fun add(item: T)                       // producer side — push an item
    public fun close(cause: Throwable? = null)    // signal Complete (or Error if cause != null)
    public fun asChannel(): Channel<T>            // hand the producer a plain SendChannel
}

(docs/CORPUS.md §G; R5.) It turns "was this callback invoked with X, and only that?" into the same awaitItem() vocabulary:

kotlin
@Test fun emitsClickedId() = runTest(mainRule.dispatcher) {
    val clicks = Turbine<Long>()
    val adapter = ItemAdapter(onClick = clicks::add)        // production code calls onClick(id)

    adapter.bind(item)
    adapter.simulateClick()

    assertEquals(42L, clicks.awaitItem())
    clicks.ensureAllEventsConsumed()                        // no extra invocations slipped through
}

ensureAllEventsConsumed() asserts nothing is left buffered — the standalone-Turbine equivalent of the check flow.test { } runs implicitly on block exit. A standalone Turbine never auto-completes, so awaitComplete() on one hangs to the timeout unless you close() it; treat it like a hot flow.

Synchronous (non-suspend) consumption: takeItem / takeComplete / takeError

When the assertion site cannot suspend — a plain @Test body after the producing coroutine already ran, an assert inside a non-suspend callback — ReceiveTurbine exposes non-suspending takers that require the event to be buffered already and throw if it is not:

kotlin
public fun takeItem(): T
public fun takeComplete()
public fun takeError(): Throwable
kotlin
@Test fun synchronousDrain() = runTest(mainRule.dispatcher) {
    val turbine = vm.uiState.testIn(backgroundScope)
    advanceUntilIdle()                              // let the producer run to quiescence (virtual time)
    assertEquals(Idle, turbine.takeItem())          // no suspension — the value is already buffered
    assertEquals(Loaded, turbine.takeItem())
    turbine.cancel()
}

Prefer suspending awaitItem() inside runTest / flow.test blocks; reach for takeItem() only when the call site genuinely cannot suspend.

Turbine timeout vs runTest virtual time

Turbine's 3-second default is wall clock, never virtual. runTest's 60-second default is also wall clock — but advanceTimeBy(...) inside the test body manipulates TestCoroutineScheduler virtual time, which Turbine does not see.

Implication: a test using delay(10.minutes) finishes in microseconds because runTest skips the delay. But a flow.test { awaitItem() } that waits for an emission whose source is real network I/O still observes real wall-clock time. If the wait is genuinely longer than 3 seconds in real time, override:

kotlin
flow.test(timeout = 10.seconds) { … }            // per-call

withTurbineTimeout(10.seconds) {                 // scoped
    flow.test { … }
}
Show full SKILL.md (629 more words)Show less

Decision matrix — Turbine vs raw flow.toList()

ScenarioPick
Cold flow that completes; "the full list equals X"flow.toList() (shorter)
Hot StateFlow / SharedFlowTurbine (handles cancellation)
Need to assert intermediate state then trigger more emissionsTurbine
Need to assert "no more events for a while"Turbine (expectNoEvents)
Need to assert error type / completionTurbine (awaitError, awaitComplete)
Multiple flows interleaved in one testTurbine (turbineScope/testIn)
Multiplatform test on JS/NativeBoth work; Turbine is easier
Zero extra dependencyflow.toList()

(R5.)

Rule of thumb: prefer Turbine for any UI-state / ViewModel test (almost always StateFlow-driven), and flow.toList() for repository/transformation tests of cold flows that complete deterministically.

Patterns

Pattern: WRONG — flow.toList() on a SharedFlow
kotlin
// WRONG
@Test fun events() = runTest {
    val events: SharedFlow<Event> = vm.events
    val collected = events.toList()                 // never returns
    assertEquals(listOf(LoggedIn), collected)
}
// WRONG because: SharedFlow never completes. toList() suspends until the upstream
// completes, which it never does. The test hangs to the 60s runTest timeout.
kotlin
// RIGHT
@Test fun events() = runTest(mainRule.dispatcher) {
    vm.events.test {
        vm.login()
        assertEquals(LoggedIn, awaitItem())
        cancelAndIgnoreRemainingEvents()             // hot flow — must cancel
    }
}
Pattern: WRONG — forgetting StateFlow's seed value
kotlin
// WRONG
@Test fun stateFlowEmits() = runTest(mainRule.dispatcher) {
    vm.uiState.test {
        vm.refresh()
        assertEquals(Loading, awaitItem())           // FAIL: actually Idle (seed)
        cancel()
    }
}
// WRONG because: StateFlow has replay = 1. The collector immediately receives the
// current value (Idle) before any new emission. The first awaitItem() returns
// the seed, not the post-refresh state.
kotlin
// RIGHT — assert the seed
@Test fun stateFlowEmits() = runTest(mainRule.dispatcher) {
    vm.uiState.test {
        assertEquals(Idle, awaitItem())              // seed
        vm.refresh()
        assertEquals(Loading, awaitItem())
        assertEquals(Success(items), awaitItem())
        cancelAndIgnoreRemainingEvents()
    }
}

// RIGHT — skip the seed if you do not care
@Test fun stateFlowEmits_skipSeed() = runTest(mainRule.dispatcher) {
    vm.uiState.test {
        skipItems(1)
        vm.refresh()
        assertEquals(Loading, awaitItem())
        assertEquals(Success(items), awaitItem())
        cancelAndIgnoreRemainingEvents()
    }
}
Pattern: WRONG — awaitComplete() on a hot flow
kotlin
// WRONG
@Test fun events() = runTest {
    vm.events.test {
        vm.login()
        assertEquals(LoggedIn, awaitItem())
        awaitComplete()                              // hangs 3s, then TurbineAssertionError
    }
}
// WRONG because: SharedFlow never completes. awaitComplete() waits 3 wall-clock
// seconds (Turbine default) and then throws. The test is slow AND fails.
kotlin
// RIGHT
@Test fun events() = runTest(mainRule.dispatcher) {
    vm.events.test {
        vm.login()
        assertEquals(LoggedIn, awaitItem())
        cancelAndIgnoreRemainingEvents()
    }
}
Pattern: conflated StateFlow — use expectMostRecentItem
kotlin
@Test fun finalState() = runTest(mainRule.dispatcher) {
    vm.uiState.test {
        skipItems(1)                                 // seed
        vm.flipFast()                                // posts 5 transitions in <1ms
        advanceUntilIdle()                           // virtual; let coroutines settle
        // StateFlow conflates — only the latest value is kept.
        // awaitItem() may see anything from the burst; expectMostRecentItem is deterministic.
        assertEquals(Final, expectMostRecentItem())
        cancelAndIgnoreRemainingEvents()
    }
}
Pattern: assert "no more events"
kotlin
@Test fun noSpuriousEmissions() = runTest(mainRule.dispatcher) {
    vm.uiState.test {
        skipItems(1)
        vm.refresh()
        assertEquals(Loading, awaitItem())
        assertEquals(Success(items), awaitItem())
        advanceTimeBy(5.seconds)                     // virtual — flush any timers
        runCurrent()
        expectNoEvents()                             // confirms quiescence
        cancelAndIgnoreRemainingEvents()
    }
}
Pattern: cold flow that completes — Turbine vs toList
kotlin
// FINE — toList() is shorter for terminal cold flows
@Test fun coldList() = runTest {
    val collected = flowOf(1, 2, 3).toList()
    assertEquals(listOf(1, 2, 3), collected)
}

// ALSO FINE — Turbine if you need intermediate-state assertions
@Test fun coldTurbine() = runTest {
    flowOf(1, 2, 3).test {
        assertEquals(1, awaitItem())
        assertEquals(2, awaitItem())
        assertEquals(3, awaitItem())
        awaitComplete()                              // cold flow — completes naturally
    }
}
Pattern: two flows interleaved with turbineScope
kotlin
@Test fun interleaved() = runTest(mainRule.dispatcher) {
    turbineScope {
        val state = vm.uiState.testIn(backgroundScope, name = "state")
        val events = vm.events.testIn(backgroundScope, name = "events")
        assertEquals(Idle, state.awaitItem())
        vm.login()
        assertEquals(Loading, state.awaitItem())
        assertEquals(LoggedIn, events.awaitItem())
        assertEquals(Success, state.awaitItem())
        state.cancel(); events.cancel()
    }
}

Mandatory rules

  • MUST wrap any StateFlow / SharedFlow assertion in flow.test { … cancelAndIgnoreRemainingEvents() }. MUST NOT call flow.toList() on a hot flow — the test hangs.
  • MUST call cancel() / cancelAndIgnoreRemainingEvents() at the end of every test { } block over a hot flow. awaitComplete() is wrong for hot flows — they never complete.
  • MUST account for StateFlow's seed value: the first awaitItem() returns the current value (replay = 1). Either assert it or skipItems(1).
  • MUST depend on Turbine from testImplementation only. MUST NOT put app.cash.turbine on implementation.
  • MUST override withTurbineTimeout(...) (or per-call timeout =) when the awaited emission genuinely takes more than 3 wall-clock seconds. The 3 s default is wall clock and is independent from runTest virtual time.
  • MUST use cancel() (NOT awaitComplete()) inside testIn blocks before turbineScope ends; otherwise the scope assertion throws.
  • MUST NOT mix Turbine timeouts with runTest's virtual clock semantics. advanceTimeBy(10.minutes) is virtual; Turbine's 3-second wait is real.
  • PREFERRED: expectMostRecentItem() for deterministic "final state" assertions on conflated StateFlow where intermediate values may be collapsed.
  • PREFERRED: raw flow.toList() for cold flows that complete deterministically — Turbine is overhead in that case.
  • PREFERRED: name turbines (flow.test(name = "uiState"), flow.testIn(scope, name = "events")) when more than one runs in a test — error messages cite the name.
  • PREFERRED: the standalone Turbine<T>() (with add / close / ensureAllEventsConsumed) over a manually accumulated MutableList when verifying a (T) -> Unit callback or a listener was invoked — it gives the same awaitItem() diagnostics and the unconsumed-event check.
  • PREFERRED: suspending awaitItem() inside runTest / flow.test blocks over the non-suspending takeItem() / takeComplete() / takeError(); reach for the takers only when the call site genuinely cannot suspend.

Verification

  • No flow.toList() call exists on any StateFlow or SharedFlow (grep -rn 'toList()' src/test cross-checked against flow types).
  • Every flow.test { … } over a hot flow ends with cancel() or cancelAndIgnoreRemainingEvents().
  • No awaitComplete() is called on a StateFlow / SharedFlow.
  • StateFlow tests either assert the seed via awaitItem() or skipItems(1) before further assertions.
  • Turbines that may legitimately wait > 3 s use withTurbineTimeout(...) or per-call timeout = N.seconds.
  • Multi-flow tests use turbineScope { … testIn(backgroundScope) … } rather than nested test { } blocks.
  • Callback / listener assertions use a standalone Turbine<T>() (with ensureAllEventsConsumed()), not an ad-hoc MutableList.
  • CI run completes 50 iterations without TurbineAssertionError flakes.

References

  • Cash App Turbine — README and API: https://github.com/cashapp/turbine
  • Cash App Turbine — releases: https://github.com/cashapp/turbine/releases
  • Android Developers — Test Kotlin Flow on Android: https://developer.android.com/kotlin/flow/test
  • Android Developers — StateFlow & SharedFlow: https://developer.android.com/kotlin/flow/stateflow-and-sharedflow
  • JetBrains — Asynchronous Flow: https://kotlinlang.org/docs/flow.html
  • Research: tasks/research/R5-coroutines-test-turbine.md — Turbine API surface, hot/cold contract, comparison with toList().
  • docs/CORPUS.md §G.1 — Gradle coordinates and version pin.
  • Sibling: ../testing-coroutines-with-runtest/SKILL.md — runTest, MainDispatcherRule, virtual time foundations Turbine sits on top of.
  • Sibling: ../../mocking/mocking-with-mockito/SKILL.md — mocking suspend dependencies that produce flows.
  • Sibling: ../../mocking/mocking-with-mockk/SKILL.md — coEvery { repo.items() } returns flowOf(...).
  • Sibling: ../../runner/configuring-junit4-on-android/SKILL.md — JUnit4 plumbing.
  • Sibling: ../../robolectric/using-robolectric-correctly/SKILL.md — when the SUT needs Android framework on the JVM.
  • Cross-set: ../../../fundamentals/strategies/applying-testing-strategies/SKILL.md — small/medium/big sizing for Flow tests.
  • Cross-set: ../../../fundamentals/strategies/organizing-test-source-sets/SKILL.md — where Flow tests belong (src/test/).
  • Cross-set: ../../../compose/synchronization/controlling-the-test-clock/SKILL.md — Compose's MainTestClock is independent of Turbine's wall clock.
  • Cross-set: ../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md — Turbine works on-device too, with the same API.
  • Cross-set: ../../../kotlin/kotlin-test/writing-tests-with-kotlin-test/SKILL.md — awaitItem() paired with kotlin.test's assertEquals / assertIs; the assertion vocabulary inside flow.test { }.

© skydoves, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in jvm-tests/coroutines/testing-flows-with-turbine of skydoves/android-testing-skills.

Open the folder on GitHubat commit 8665ed5

Compare with similar skills

Testing Flows With Turbine next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Testing Flows With Turbine compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Testing Flows With Turbine this skillskydoves/android-testing-skills333—~5.5kAutomated safety check: PassApache-2.0
React Native Best Practicesvercel-labs/openreview1.7k17 repos~1.1kAutomated safety check: PassMIT
Swiftui Protwostraws/SwiftUI-Agent-Skill5.2k2 repos~1.5kAutomated safety check: PassMIT
Kortix Brandkortix-ai/suna20k—~4kAutomated safety check: PassCustom licence
Ip As LogoKartikLabhshetwar/better-shot2.4k1 repos~4.3kAutomated safety check: PassMIT
Compose Multiplatform Patternsmonta-app/ocpp-emulator1805 repos~2kAutomated safety check: PassApache-2.0

Similar skills

  • React Native Best Practices

    vercel-labs/openreview

    Official

    A prioritized rule set for React Native and Expo apps covering list performance, animation, navigation, UI patterns, state, rendering, monorepos and configuration.

    1.7k GitHub starsUsed in 17 repos~1.1k tokens
    MobileAuto-check passed
  • Swiftui Pro

    twostraws/SwiftUI-Agent-Skill

    Comprehensively reviews SwiftUI code for best practices on modern APIs, maintainability, and performance.

    5.2k GitHub starsUsed in 2 repos~1.5k tokens
    MobileAuto-check passed
  • Kortix Brand

    kortix-ai/suna

    Load FIRST for anything that carries the Kortix look or voice: product or mobile UI, copy of any kind, decks, social, images, email, CLI output, anything with the logo, and reviews of these.

    20k GitHub stars~4k tokensUpdated today
    MobileAuto-check passed
  • Ip As Logo

    KartikLabhshetwar/better-shot

    Generate extremely simple, cute, personified square character images with rounded heavy forms, two purposeful character colors, one solid background color, and a dominant lower-corner composition.

    2.4k GitHub starsUsed in 1 repo~4.3k tokens
    MobileAuto-check passed
  • Compose Multiplatform Patterns

    monta-app/ocpp-emulator

    Compose Multiplatform and Jetpack Compose patterns for KMP projects — state management, navigation, theming, performance, and platform-specific UI.

    180 GitHub starsUsed in 5 repos~2k tokens
    MobileAuto-check passed
  • Aso Appstore Screenshots

    adamlyttleapps/claude-skill-aso-appstore-screenshots

    Generate high-converting App Store screenshots by analyzing your app's codebase, discovering core benefits, and creating ASO-optimized screenshot images using Nano Banana Pro.

    1.8k GitHub starsUsed in 1 repo~9.6k tokens
    MobileAuto-check passed

More from skydoves/android-testing-skills

All 50 skills in this repo
  • Asserting Bounds And Dimensions

    skydoves/android-testing-skills

    A skill your agent uses to verify Compose layout measurements from a UI test using assertWidthIsEqualTo, assertHeightIsEqualTo, assertWidthIsAtLeast, assertHeightIsAtLeast…

    333 GitHub stars~3.3k tokensUpdated 4 mo ago
    Auto-check passed
  • Asserting Node State And Text

    skydoves/android-testing-skills

    A skill your agent uses to verify a Compose semantics node's properties from a UI test using assertExists, assertDoesNotExist, assertIsDisplayed, assertIsNotDisplayed, assertIsDeactivated…

    333 GitHub stars~3.6k tokensUpdated 4 mo ago
    Auto-check passed
  • Capturing Preview Screenshots In CI

    skydoves/android-testing-skills

    A skill your agent uses to render every Jetpack Compose @Preview as a screenshot on a real Android device or emulator and publish a browsable HTML catalog from CI.

    333 GitHub stars~4.5k tokensUpdated 4 mo ago
    Auto-check: notes
  • Capturing Screenshots And Screenrecord

    skydoves/android-testing-skills

    A skill your agent uses to capture visual artefacts from a device for test failures, golden image generation, QA repro, and demo videos.

    333 GitHub stars~3.7k tokensUpdated 4 mo ago
    Auto-check passed
  • Choosing Test Rule Vs Runtest

    skydoves/android-testing-skills

    A skill your agent uses to pick the correct Compose UI test entry point.

    333 GitHub stars~4.5k tokensUpdated 4 mo ago
    Auto-check passed
  • Choosing What To Test

    skydoves/android-testing-skills

    A skill your agent uses to pick which behaviors to cover in an Android test suite using Google's five-category state vocabulary plus the explicit "what NOT to test" list from…

    333 GitHub stars~4.6k tokensUpdated 4 mo ago
    Auto-check passed

Categories

Questions about Testing Flows With Turbine

What does Testing Flows With Turbine do?

A skill your agent uses to assert Flow emissions in tests with Cash App Turbine. Testing Flows With Turbine is an agent skill from skydoves/android-testing-skills. Use this skill to assert Flow emissions in tests with Cash App Turbine.

When should I use Testing Flows With Turbine?

Testing Flows With Turbine fits situations like: assert Flow emissions in tests with Cash App Turbine.

How do I install Testing Flows With Turbine in Claude Code?

Run `npx skills add skydoves/android-testing-skills --skill testing-flows-with-turbine -a claude-code`. Or copy the skill folder (jvm-tests/coroutines/testing-flows-with-turbine in skydoves/android-testing-skills) into .claude/skills/testing-flows-with-turbine in your project. Claude Code loads it when a task matches its description.

How do I install Testing Flows With Turbine in Codex?

Run `npx skills add skydoves/android-testing-skills --skill testing-flows-with-turbine -a codex`. Or copy the skill folder (jvm-tests/coroutines/testing-flows-with-turbine in skydoves/android-testing-skills) into .agents/skills/testing-flows-with-turbine in your project. Codex loads it when a task matches its description.

Can I use Testing Flows With Turbine in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add skydoves/android-testing-skills --skill testing-flows-with-turbine -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/testing-flows-with-turbine, .gemini/skills/testing-flows-with-turbine, .github/skills/testing-flows-with-turbine and .opencode/skills/testing-flows-with-turbine in your project.

What does Testing Flows With Turbine need to run?

SKILL.md names no scripts, command-line tools or credentials: Testing Flows With Turbine is instructions for the agent only.

Does Testing Flows With Turbine access the network?

SKILL.md names 3 domains. As links in the text: github.com, developer.android.com and kotlinlang.org. This is read from the text; nothing was executed.

Is Testing Flows With Turbine safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Testing Flows With Turbine use?

Testing Flows With Turbine is published under the Apache-2.0 licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Testing Flows With Turbine use?

About 5.5k tokens (SKILL.md is roughly 22k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Testing Flows With Turbine?

Skills that share tags, products or a category with Testing Flows With Turbine: React Native Best Practices (vercel-labs/openreview, 1.7k stars), Swiftui Pro (twostraws/SwiftUI-Agent-Skill, 5.2k stars), Kortix Brand (kortix-ai/suna, 20k stars) and Ip As Logo (KartikLabhshetwar/better-shot, 2.4k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Testing Flows With Turbine?

skydoves (a GitHub user) maintains it in skydoves/android-testing-skills, which has 333 GitHub stars. The repository holds 50 skills in this directory. The repository was last updated on May 25, 2026.

Source: skydoves/android-testing-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.