How do you collect all emissions from a finite Flow in a test and assert the exact sequence of values?
answer
- runTest gives a coroutine scope for suspend calls
- toList() = terminal, suspends to completion, returns ordered List
- assertEquals(expected, actual) — expected first
- List equality is order-sensitive
- Only for finite/completing flows
basics
~10 sCall 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 sWrap 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@Test
fun mapsValues() = runTest {
val result = flowOf(1, 2, 3)
.map { it * 10 }
.toList()
assertEquals(listOf(10, 20, 30), result)
}go deeper
Knows to wrap in runTest, call toList(), and assertEquals against a literal list.
Explains that toList() is a terminal suspend operator that preserves order and only suits finite flows.
Discusses runTest's virtual time, why infinite flows hang, and expected-first convention for readable diffs.
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