skip to content

What does cancelAndIgnoreRemainingEvents() do, and why is it usually required when testing a StateFlow or other hot flow with Turbine?

level: seniorimportance: must knowfreq 55%

answer

  1. Cancels collector + drops leftover events
  2. Hot flows (StateFlow/SharedFlow) never Complete
  3. Avoids the unconsumed-events failure
  4. Sibling: cancelAndConsumeRemainingEvents returns the list
  5. StateFlow conflates -> may only see latest value

basics

~20 s

It stops Turbine from collecting the flow and throws away any values still waiting. Hot flows like StateFlow never end on their own, so you call it to finish the test cleanly without failing on leftover events.

solid answer

~40 s

cancelAndIgnoreRemainingEvents() cancels Turbine's internal collecting coroutine and discards every unconsumed event so the test block can exit without the 'unconsumed events' failure. It's essential for hot, non-terminating flows — StateFlow, SharedFlow, or any flow that keeps emitting — because they never produce a Complete event, so you can't end with awaitComplete(), and any values you didn't await would otherwise fail the test. The contrast is cancelAndConsumeRemainingEvents(), which returns the leftover events as a List for inspection, and cancel(), the lower-level cancel. Pattern: await the values you care about (often the StateFlow's current value), assert them, then cancelAndIgnoreRemainingEvents() to drain and stop. Turbine still enforces that you handled the events you explicitly awaited; this call only forgives the *remaining* ones you intentionally don't care about.

code

kotlin · 9 lines
kotlin
@Test
fun sharedFlowEmissions() = runTest {
    val events = MutableSharedFlow<String>()
    events.test {
        events.emit("a")
        assertEquals("a", awaitItem())
        cancelAndIgnoreRemainingEvents() // SharedFlow never completes
    }
}

go deeper

for a junior

Knows it stops collection so hot-flow tests don't hang or fail on leftovers.

for a middle

Explains hot flows never complete and the unconsumed-events check, choosing cancel over awaitComplete.

for a senior

Distinguishes Ignore vs Consume variants and accounts for StateFlow conflation when designing the assertions.

for a principal

Sets team conventions: when cancel is appropriate vs a smell, and how conflation/virtual-time interplay can cause flaky hot-flow tests.

## The problem with hot flows Turbine collects the flow in a background coroutine. When the `test { }` block ends, Turbine checks that **all events were consumed** and that a terminal event (Complete/Error) was awaited — leftovers are a **test failure**. That's great for finite **cold** flows. But **hot** flows are different: - **`StateFlow`** always holds a current value and **never completes**. - **`SharedFlow`** broadcasts and **never completes** on its own. - Any `flow { while(true) { ... } }` style stream never terminates. There is no Complete event to await, and the producer may keep adding items you don't care about. Ending the block would fail. ## What `cancelAndIgnoreRemainingEvents()` does It (1) **cancels** Turbine's collecting coroutine, stopping further collection, and (2) **ignores/discards** every event still in the queue, so the end-of-block consumption check passes. It's the clean way to say "I've asserted what I needed; stop watching this endless flow." ```kotlin @Test fun stateFlowCurrentValue() = runTest { val state = MutableStateFlow(0) state.test { assertEquals(0, awaitItem()) // StateFlow replays current value state.value = 1 assertEquals(1, awaitItem()) cancelAndIgnoreRemainingEvents() // never completes -> must cancel } } ``` ## Related APIs (know the differences) - **`cancelAndConsumeRemainingEvents(): List<Event<T>>`** — cancels but **returns** the remaining events so you can assert on them. - **`cancel()`** — cancels the underlying collection without the consumption guarantee (lower level). - **`cancelAndIgnoreRemainingEvents()`** — cancels and silently drops the rest (most common for hot flows). ## StateFlow conflation nuance `StateFlow` is **conflated** and emits `distinctUntilChanged` by default. If you set `value` to several values faster than the collector runs, Turbine may only observe the **latest**. Under `runTest`, advance/`runCurrent()` between mutations if you need each intermediate value to be observed. This interacts with cancel: you await the values you can actually observe, then ignore the rest. ## What it does NOT excuse It only forgives the **remaining** (un-awaited) events. Events you explicitly `awaitItem()`-ed still must match. And if a flow *does* complete normally, prefer `awaitComplete()` — calling cancel on a finite flow you should have asserted to completion hides bugs. ## Rule of thumb - Cold finite flow → end with `awaitComplete()` / `awaitError()`. - Hot / infinite flow → assert what you need, then `cancelAndIgnoreRemainingEvents()`.

  • How is cancelAndIgnoreRemainingEvents() different from cancelAndConsumeRemainingEvents()?
    Both cancel collection, but ConsumeRemaining returns the leftover events as a List<Event<T>> for assertions, while IgnoreRemaining simply discards them.
  • Why might you only observe the latest value when rapidly updating a StateFlow in a test?
    StateFlow is conflated and distinctUntilChanged; updates faster than the collector runs are coalesced. Advance virtual time (runCurrent) between updates to observe intermediates.

It's hanging up the phone on an open line that will never say goodbye — you got what you needed, so you end the call deliberately.

saying these in an interview costs you the question

  • Using awaitComplete() on a StateFlow expecting it to end
  • Believing cancelAndIgnoreRemainingEvents forgives awaited-but-wrong events
  • Not knowing the Consume vs Ignore variant
  • Ignoring StateFlow conflation when expecting every intermediate value
  • Calling cancel on a finite flow that should be asserted to completion

context