skip to content

How do you collect all emissions from a finite Flow in a test and assert the exact sequence of values?

level: juniorimportance: must knowfreq 78%

answer

  1. runTest gives a coroutine scope for suspend calls
  2. toList() = terminal, suspends to completion, returns ordered List
  3. assertEquals(expected, actual) — expected first
  4. List equality is order-sensitive
  5. Only for finite/completing flows

basics

~10 s

Call toList() on the flow inside a runTest block to gather every emitted value into a List, then compare it to the expected list with assertEquals(expected, actual).

solid answer

~40 s

Wrap the test in runTest { } so suspend functions like Flow.toList() can be called. toList() is a terminal operator that suspends until the flow completes, collecting every emission into a List in emission order. Assert with assertEquals(expected, actual) where expected is the literal list you anticipate, e.g. assertEquals(listOf(1, 2, 3), flowOf(1, 2, 3).toList()). Because List equality is order-sensitive and element-wise, this checks both the values and their order. Only use toList() for finite flows that complete on their own; an infinite flow (e.g. a hot StateFlow) would hang the test. Keep the expected value first in assertEquals so failure messages read 'expected ... but was ...' correctly.

code

kotlin · 7 lines
kotlin
@Test
fun mapsValues() = runTest {
    val result = flowOf(1, 2, 3)
        .map { it * 10 }
        .toList()
    assertEquals(listOf(10, 20, 30), result)
}

go deeper

for a junior

Knows to wrap in runTest, call toList(), and assertEquals against a literal list.

for a middle

Explains that toList() is a terminal suspend operator that preserves order and only suits finite flows.

for a senior

Discusses runTest's virtual time, why infinite flows hang, and expected-first convention for readable diffs.

for a principal

Frames choice of collection strategy (toList vs take vs first vs Turbine) as a testability concern and guides team conventions.

## What we are testing A `Flow<T>` in Kotlin coroutines is a cold asynchronous stream: nothing runs until a **terminal operator** starts collection. To test a flow we need to (1) run it to completion, (2) capture what it emitted, and (3) compare that against what we expected. ## `runTest` — the test coroutine builder `Flow.toList()` is a `suspend` function (it can pause and resume), so it can only be called from a coroutine. `runTest { }` (from `kotlinx-coroutines-test`) creates a coroutine scope for tests. Inside it you can call suspend functions directly. It also uses virtual/skipped time, so delays don't slow the test down. ```kotlin import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals class NumbersTest { @Test fun emitsThreeNumbers() = runTest { val flow = flowOf(1, 2, 3) val result = flow.toList() // terminal operator, suspends until done assertEquals(listOf(1, 2, 3), result) // expected first, actual second } } ``` ## `toList()` — the terminal collector - It is a **terminal operator**: it triggers collection and consumes the whole stream. - It **suspends** until the flow naturally completes, then returns a `List<T>` in emission order. - It is the standard way to snapshot a finite flow's full output. - You can pass an existing mutable list: `flow.toList(destination)`. ## Asserting with `assertEquals` `assertEquals(expected, actual)` from `kotlin.test` uses `==` (structural equality). For `List`, equality is **ordered and element-wise**: `listOf(1, 2)` equals `listOf(1, 2)` but not `listOf(2, 1)`. So a single `assertEquals` simultaneously checks the values AND their order. Convention: put the **expected** value first so the failure message reads `expected:<...> but was:<...>`. ## Finite vs infinite `toList()` only works on flows that **complete**. A flow built from a never-ending source (e.g. `MutableStateFlow`, an infinite `flow { while(true) ... }`) will never finish, so `toList()` would suspend forever and the test would hang/time out. For those you take a bounded prefix or use other tools. ## Recap of keywords `suspend`, `Flow`, terminal operator, `runTest`, `toList()`, `assertEquals`, structural equality (`==`).

  • What happens if you call toList() on an infinite flow inside runTest?
    It never returns because the flow never completes; runTest will hang and eventually fail with a timeout. Use take(n).toList() or first() to bound it.
  • Does assertEquals on two lists check ordering?
    Yes. List.equals is order-sensitive and element-by-element, so the assertion fails if values differ OR if the order differs.

toList() is like letting a vending machine dispense every item it will give, then counting them in the order they dropped.

saying these in an interview costs you the question

  • Calling toList() outside any coroutine/runTest and expecting it to compile
  • Claiming toList() ignores emission order
  • Using toList() on a StateFlow or infinite flow
  • Putting actual before expected and misreading failure messages
  • Thinking toList() returns before the flow completes

context