When testing a StateFlow in a runTest block, how can you assert its current value without collecting it, and why does that work for StateFlow but not a plain Flow?
answer
- StateFlow has .value, plain Flow does not
- Hot + conflated state holder
- No collector needed for current value
- SharedFlow uses replayCache, not .value
- runTest gives virtual time but .value read is synchronous
basics
~10 sRead stateFlow.value. StateFlow always holds a current value you can read directly, so you just assert on it. A plain Flow has no stored value, so there is nothing to read.
solid answer
~40 sStateFlow extends Flow but also exposes a synchronous .value property holding its latest state. In a runTest block you can drive the system under test (call the function that updates the StateFlow), then assert assertEquals(expected, stateFlow.value) with no collector at all. This works because StateFlow is a state holder: it always has exactly one current value and conflates updates to the newest one. A cold Flow (and a base SharedFlow without replay) keeps no stored value, so .value does not exist there — you must collect emissions to observe them. For StateFlow, reading .value is the simplest, most deterministic assertion and avoids needing virtual time or a collection coroutine when you only care about the final/current state.
code
kotlin · 6 lines@Test
fun toggles() = runTest {
val flow = MutableStateFlow(false)
flow.value = true
assertTrue(flow.value) // direct read, no collection
}go deeper
Knows StateFlow.value exists and can assert on it directly in a test.
Explains why .value works (hot conflated state holder) and that plain Flow/SharedFlow lack it.
Distinguishes when a direct .value read suffices vs needing advanceUntilIdle, and notes SharedFlow.replayCache.
Frames .value reads as the deterministic default for final-state assertions and reserves collection for intermediate-sequence verification across a test suite.
## What StateFlow is `StateFlow<T>` is a **hot**, **conflated** flow that always holds exactly one value. "Hot" means it exists and holds state independently of collectors (unlike a cold `Flow`, which runs its producer fresh per collector). It exposes a synchronous read-only property: ```kotlin public val value: T ``` `MutableStateFlow<T>` adds a settable `value` (and `compareAndSet`). Because the value is always present, you can read it in a test **without any collector and without advancing virtual time**. ## Asserting in a test ```kotlin @Test fun increments() = runTest { val vm = CounterViewModel() // exposes val state: StateFlow<Int> vm.increment() // mutates the underlying MutableStateFlow assertEquals(1, vm.state.value) // direct, synchronous read } ``` `runTest` provides a `TestScope` with a virtual-time scheduler, but for a pure `.value` read you do not even need to call `advanceUntilIdle()` unless the update happens inside a launched coroutine that must run first. ## Why a plain Flow can't do this A cold `Flow<T>` has no stored state — it is a recipe that emits when collected. There is no `.value`. To observe a `Flow`, you must collect it (e.g. `.toList()` or a launched `collect`). Likewise a base `SharedFlow` with `replay = 0` keeps no value to read. ## StateFlow vs SharedFlow for this - `StateFlow` → has `.value` (replay = 1 conceptually, plus initial value required). Read it directly. - `SharedFlow` → no `.value`. Even with `replay = 1` you read the buffered value via `replayCache.lastOrNull()`, not `.value`. ## Key takeaway For StateFlow, when the assertion is about the **current/final** state, prefer reading `.value` — it is deterministic and needs no collection machinery. Use a collector only when you must verify the **sequence** of intermediate emissions.
- What if the StateFlow is updated inside a coroutine launched by the function under test?Then call advanceUntilIdle() (or runCurrent()) so the launched coroutine runs on the virtual scheduler before you read .value.
- How do you read the latest value of a SharedFlow with replay=1?Use sharedFlow.replayCache.lastOrNull(); SharedFlow has no .value property.
StateFlow is like a thermostat display showing the current temperature; a plain Flow is a stream of readings with no display to glance at.
saying these in an interview costs you the question
- Claiming a plain Flow also has a .value property
- Spinning up a collector just to read the current state
- Confusing StateFlow.value with SharedFlow.replayCache
- Asserting .value before advancing time when the update is in a launched coroutine