skip to content

Collecting & Asserting Emissions

For a finite flow you can just collect it with toList, or bound it with take, and compare the emitted sequence. It is the simplest approach and the right one whenever the flow actually completes.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

What is the difference between flow.first(), flow.take(n).toList(), and flow.toList() when collecting in a test, and when do you use each?

level: middleimportance: must knowfreq 70%

basics

~20 s

first() grabs only the very first emission and stops. take(n).toList() collects the first n emissions then stops. toList() collects everything until the flow finishes. Use the bounded ones when the flow may not complete on its own.

open as a page

A flow uses delay() between emissions. How does runTest let you collect it with toList() without the test actually waiting in real time?

level: middleimportance: should knowfreq 58%

basics

~10 s

runTest uses a virtual clock. Delays inside the flow are skipped instantly instead of really pausing, so toList() still collects all the values but the test finishes in milliseconds.

open as a page

How do you assert that a flow emits some values and then throws, or that it emits nothing at all, when collecting with toList()?

level: seniorimportance: should knowfreq 47%

basics

~20 s

If a flow throws, toList() rethrows that exception, so wrap it in assertFailsWith. To capture values emitted before the failure, collect into a list manually with collect{}. For an empty flow, toList() returns an empty list you assert with assertEquals(emptyList(), result).

open as a page

You wrote flow.toList() and asserted an exact ordered list, but the test is flaky—sometimes the order differs. What likely causes this and how do you make collection deterministic?

level: seniorimportance: nice to knowfreq 33%

basics

~20 s

A single sequential flow emits in a fixed order, so flakiness usually means concurrency: the flow uses flatMapMerge, merge, channels, or a real dispatcher that interleaves results. Use ordering-preserving operators or sort the result before asserting.

open as a page