Assert a Flow's emission sequence with the Flow-testing library Turbine
SearchRepository.search(query) returns a Flow<UiState> that must emit exactly UiState.Loading, then UiState.Done, and then complete — nothing else.
Write the test body so it asserts that exact order AND proves that no extra item was emitted after Done. Use the Flow-testing library Turbine; the test is already driven by runTest.
@Test
fun `search emits Loading then Done`() = runTest {
val flow: Flow<UiState> = repository.search(query = "kotlin")
// your code here
}
Write the implementation.
Collect the flow in Turbine's test { } block and pull items in order: flow.test { assertEquals(UiState.Loading, awaitItem()); assertEquals(UiState.Done, awaitItem()); awaitComplete() }. awaitComplete() proves nothing extra arrived.
- ✗Asserting only the items and never closing the block, so an extra emission after
Donepasses unnoticed - ✗Reaching for
first()ortoList()on an infinite flow, which hangs instead of failing the test - ✗Treating
awaitItem()as a peek at the latest value rather than as pulling the next emission in order
- →How would the test change if the flow never completes, as a
StateFlowdoes? - →What does the Flow-testing library
Turbinereport when the flow emits one more item than you awaited?
The solution
Turbine gives you a test { } block: inside it the flow is collected and items are pulled one at a time with awaitItem(). The order is asserted by the order of the calls themselves.
@Test
fun `search emits Loading then Done`() = runTest {
val flow: Flow<UiState> = repository.search(query = "kotlin")
flow.test {
assertEquals(UiState.Loading, awaitItem())
assertEquals(UiState.Done, awaitItem())
awaitComplete() // ✅ the flow finished — no extra emissions
}
}
The last line is the point. awaitComplete() demands that the flow completes exactly here: if one more item arrives after Done, Turbine fails the test with an unexpected-emission error. Without that line the extra item would slip through unnoticed.
For a flow that never completes — a StateFlow, say — use cancelAndIgnoreRemainingEvents() instead: it cancels the collection and states that nothing further is being asserted.