What it is
Turbine is a small testing library for Kotlin Flows, providing a clear API for asserting on emissions, completion and errors.
Call .test { } on a Flow and consume emissions with awaitItem(). Turbine fails the test if items are left unconsumed or expected ones never arrive.
Installation
testImplementation("app.cash.turbine:turbine:1.1.0")Getting started
The smallest useful thing you can do with it, and what each part means.
kotlin
@Test
fun `emits loading then content`() = runTest {
viewModel.state.test {
assertEquals(UiState.Loading, awaitItem())
viewModel.load()
val ready = awaitItem() as UiState.Ready
assertEquals(3, ready.books.size)
// Fails the test if anything else was emitted.
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `propagates errors`() = runTest {
failingFlow.test {
assertEquals(1, awaitItem())
assertTrue(awaitError() is IOException)
}
}Advanced usage
Where the library earns its place over a simpler alternative.
kotlin
@Test
fun `debounces rapid input`() = runTest {
// runTest's scheduler skips delays instantly — no real waiting.
searchViewModel.results.test {
skipItems(1) // initial empty state
searchViewModel.onQueryChanged("du")
searchViewModel.onQueryChanged("dun")
searchViewModel.onQueryChanged("dune")
advanceTimeBy(301) // past the debounce window
val results = awaitItem()
assertEquals(1, results.size) // only the last query ran
cancelAndIgnoreRemainingEvents()
}
}Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- Unconsumed events found
- The flow emitted more than the test consumed. Assert on them, or end with cancelAndIgnoreRemainingEvents.
- No value produced in 3s
- The flow never emitted — often a dispatcher not controlled by the test. Inject a TestDispatcher.
Best practices
- Always finish with cancelAndIgnoreRemainingEvents or awaitComplete; unconsumed events fail the test by design.
- Use runTest and advanceTimeBy for time-based operators rather than real delays.
- Test StateFlow expecting the current value first — it replays immediately on collection.
- Keep each test to one behaviour; long emission sequences are hard to diagnose.
Background
Why it exists, and what it was reacting to.
From Cash App, Turbine solves a real problem: testing a Flow with collect and a mutable list is racy and produces confusing failures. Turbine makes each expected emission an explicit assertion.
