What is Turbine and why use flow.test { } instead of toList() when testing a Kotlin Flow?
answer
- Cash App library: app.cash.turbine
- flow.test { } gives ReceiveTurbine receiver
- awaitItem / awaitComplete / awaitError
- Works for infinite/hot flows; toList does not
- Run inside runTest for virtual time
basics
~20 sTurbine is a small testing library for Kotlin Flows. Its flow.test { } block lets you wait for and check each emitted value one at a time, instead of collecting everything into a list first.
solid answer
~40 sTurbine (from Cash App) is a coroutine-friendly testing library for Kotlin Flow. The extension flow.test { } runs the flow inside a test scope and gives you a suspending API to assert emissions in order: awaitItem() pulls the next value, awaitComplete() asserts the flow finished, awaitError() asserts it threw. Compared to flow.toList(), which only works for finite flows and hides timing, Turbine works with infinite/hot flows, asserts the exact sequence and terminal event, fails if you forget unconsumed events, and integrates with runTest's virtual time. You typically wrap it in runTest { } so delays are skipped. It makes order, completion, and error explicit rather than implicit.
code
kotlin · 8 lines@Test
fun example() = runTest {
flowOf("a", "b").test {
assertEquals("a", awaitItem())
assertEquals("b", awaitItem())
awaitComplete()
}
}go deeper
Knows Turbine tests Flows and that flow.test { } awaits items one at a time instead of toList().
Explains why toList() fails on hot/infinite flows and that Turbine asserts ordering plus terminal events.
Connects Turbine to runTest virtual time and the unconsumed-events failure as an intentional correctness guard.
Frames Turbine choice in a team testing strategy: when to assert sequences explicitly vs. collect, and how it shapes deterministic flow tests.
## What Turbine is **Turbine** is a small testing library (by Cash App, `app.cash.turbine:turbine`) for asserting on **Kotlin `Flow`** emissions. A `Flow` is a cold (or hot) asynchronous stream of values produced over time; testing it means checking *which* values come out, *in what order*, and *how the stream ends* (normally or with an error). ## The core entry point: `flow.test { }` `Flow<T>.test { }` is a `suspend` extension that **collects** the flow in the background and exposes a `ReceiveTurbine<T>` receiver. Inside the block you call suspending await functions that consume events one by one: - `awaitItem(): T` — suspends until the next emitted value, returns it (or fails on completion/error/timeout). - `awaitComplete()` — asserts the flow completed normally. - `awaitError(): Throwable` — asserts the flow terminated with an exception and returns it. - `expectNoEvents()` — asserts nothing has been emitted yet (no item, completion, or error pending). - `cancelAndIgnoreRemainingEvents()` — cancels collection and drops anything still buffered. ## Why not just `toList()`? `flow.toList()` collects **everything** into a `List`, then you assert on the list. Problems: - It **never returns for infinite/hot flows** (e.g. `StateFlow`, `MutableSharedFlow`) — they don't complete. - It **hides timing and ordering semantics** behind one bulk assertion. - It doesn't distinguish *normal completion* from *error* cleanly. - It can mask **unconsumed events**; Turbine, by contrast, **fails the test if events are left unhandled** at the end of the block, catching accidental extra emissions. ## Typical usage with `runTest` Turbine requires a coroutine context. You run it inside `kotlinx.coroutines.test.runTest { }`, which provides a `TestScope` with **virtual time** so `delay()` is skipped automatically. ```kotlin @Test fun emitsThenCompletes() = runTest { flowOf(1, 2, 3).test { assertEquals(1, awaitItem()) assertEquals(2, awaitItem()) assertEquals(3, awaitItem()) awaitComplete() } } ``` Here the cold `flowOf` emits 1, 2, 3 and then **completes**, so we await three items and one completion. If we forgot `awaitComplete()` (or an item), Turbine would report unconsumed events and fail. ## Key takeaways - Turbine = ordered, explicit, terminal-aware Flow assertions. - `awaitItem`/`awaitComplete`/`awaitError` make the contract visible. - Unconsumed events are a **test failure**, which is a feature.
- Why does Turbine fail if you don't consume every event?It treats leftover items/terminal events as a sign your test missed something; forcing you to assert each one prevents silently passing tests that ignore extra emissions.
- Does flow.test { } work on a StateFlow?Yes — that's a main reason to use it. StateFlow never completes, so toList() would hang, but test { } lets you await the current value and then cancel.
toList() is reading a whole transcript after a call ends; flow.test { } is listening live and confirming each word as it's spoken.
saying these in an interview costs you the question
- Claiming Turbine replaces runTest (it runs inside it)
- Saying toList() works fine for StateFlow/SharedFlow
- Not knowing flow.test exposes await* functions
- Thinking unconsumed events are silently ignored