With a StandardTestDispatcher, a coroutine you launch inside runTest doesn't run immediately. How do runCurrent() and advanceUntilIdle() relate to making it execute?
answer
- Standard = lazy: launch is queued, not run
- runCurrent flushes now-due tasks
- advanceUntilIdle drains everything
- runTest auto-drains when the body ends
- Unconfined = eager, runs to first suspension immediately
basics
~10 sStandardTestDispatcher queues launched coroutines instead of running them right away. runCurrent() runs what's queued now; advanceUntilIdle() runs everything to the end. Without one of them, the launched code may never execute before your assertions.
solid answer
~40 srunTest defaults to StandardTestDispatcher, which is 'lazy': a child launch/async is enqueued on the scheduler at the current virtual time but does NOT start until the test yields control. runCurrent() executes those now-due tasks (running each child up to its first suspension), and advanceUntilIdle() drains everything including delays. So after launch { ... }, you typically call runCurrent() to flush immediate work, or advance the clock to let delays elapse. This explicit stepping is what makes Standard deterministic and order-controlled. Contrast UnconfinedTestDispatcher, which starts coroutines eagerly until their first suspension — there a launched body runs immediately without runCurrent, but you lose fine-grained ordering control. Knowing this prevents 'my assertion ran before the coroutine did' failures.
code
kotlin · 15 lines@Test
fun standardVsUnconfined() = runTest { // StandardTestDispatcher
var hits = 0
launch { hits++ }
assertEquals(0, hits) // queued, not run
runCurrent()
assertEquals(1, hits) // now flushed
}
@Test
fun eager() = runTest(UnconfinedTestDispatcher()) {
var hits = 0
launch { hits++ } // runs immediately to first suspension
assertEquals(1, hits) // already 1, no runCurrent needed
}go deeper
Knows you sometimes must call runCurrent/advanceUntilIdle for launched code to run.
Explains Standard is lazy/queued and picks runCurrent vs advanceUntilIdle correctly.
Articulates the Standard-vs-Unconfined eager/lazy contrast and why Standard is the deterministic default.
Weighs determinism vs convenience across a test suite and sets dispatcher conventions to avoid order-hiding bugs.
## Why the launched coroutine 'doesn't run' `runTest` uses a **StandardTestDispatcher** by default. Its scheduling policy is **lazy/fair**: when you `launch { ... }` (or `async`), the new coroutine is **queued** on the `TestCoroutineScheduler` at the current virtual time rather than executed inline. The parent (your test body) keeps running until *it* suspends or finishes. So immediately after `launch`, the child hasn't started — assertions about its side effects would fail. ```kotlin @Test fun mustFlush() = runTest { var ran = false launch { ran = true } // queued, not executed yet assertFalse(ran) // still false! runCurrent() // now the queued task runs assertTrue(ran) } ``` ## runCurrent() vs advanceUntilIdle() here - **runCurrent()** runs all tasks due at the **current** virtual time — it flushes the just-launched coroutine up to its first suspension point (e.g., first `delay`), without moving the clock past any delay. - **advanceUntilIdle()** runs everything to completion, advancing the clock through delays. Use it when you don't care about intermediate steps and just want the end state. ## Why runTest often 'just works' anyway When the **test body itself finishes or suspends**, `runTest` auto-advances and drains pending children before the test ends, so simple tests pass without explicit `runCurrent`. The explicit calls matter when you assert **mid-flight**, between launch and completion. ## Contrast: UnconfinedTestDispatcher `UnconfinedTestDispatcher` is **eager**: a `launch` runs its body immediately (on the calling thread) until the first suspension. There, `ran` would be `true` without `runCurrent()`. The tradeoff: Unconfined gives convenient eager execution but **less control over interleaving/ordering**, which can hide ordering bugs. Standard is the deterministic default for that reason. ## Practical rule - Need to assert something a child did *immediately*? `runCurrent()` after `launch`. - Need delays to elapse and everything settled? `advanceTimeBy(...)` or `advanceUntilIdle()`. - Just want the final result with no intermediate checks? Let `runTest` drain at the end, or call `advanceUntilIdle()` before final asserts.
- If runTest auto-drains at the end, why ever call runCurrent() explicitly?To assert intermediate state between launch and completion. Auto-drain only happens once the body is idle/finished; mid-flight assertions need explicit stepping.
- Why is StandardTestDispatcher the default rather than Unconfined?Standard's lazy, fair scheduling is deterministic and forces explicit control over execution order, which surfaces ordering/concurrency bugs. Unconfined's eager execution is convenient but can mask them.
Standard dispatcher is a deli ticket queue — you launch, you take a number, nothing happens until you call 'next' (runCurrent). Unconfined skips the line and serves you on the spot.
saying these in an interview costs you the question
- Believes launch always runs eagerly under StandardTestDispatcher
- Can't explain why an assertion sees stale state after launch
- Thinks runCurrent and advanceUntilIdle are interchangeable in all cases
- Reaches for Thread.sleep to 'let the coroutine run'
- Doesn't know Unconfined runs eagerly to first suspension