How do you use the scheduler's currentTime to assert timing-dependent behavior, such as verifying that a retry uses exponential backoff?
answer
- currentTime = virtual ms, deterministic
- record-before/after, assert the diff
- bracket a deadline with advanceTimeBy ±1
- advanceUntilIdle then assert final currentTime
- System.currentTimeMillis is NOT virtualized
basics
~10 scurrentTime is the virtual clock in milliseconds. Record it before and after operations and check the difference to prove how long something waited — for example that each retry waited longer than the last.
solid answer
~40 scurrentTime (on TestScope, delegating to testScheduler.currentTime) reports the virtual clock in ms. Because delays advance it deterministically, you can assert timing precisely. Capture currentTime at points of interest — e.g., record it inside a retry's failure handler each attempt — then assert the gaps match your backoff formula (100, 200, 400 ms). For a timeout, run the work, advanceUntilIdle(), and assert currentTime equals the expected deadline. Pair currentTime with advanceTimeBy to test just-before/just-after a threshold: advance to deadline-1, assert not-yet, advance 1 more, assert fired. This turns flaky wall-clock timing tests into exact, fast assertions. Avoid asserting on System.currentTimeMillis or real elapsed time; only the virtual clock is controlled.
go deeper
Knows currentTime is the virtual clock and can read it after delays.
Uses record-and-diff and threshold bracketing to assert exact durations.
Recognizes that only delay-based timing is virtualized and injects a Clock for wall-clock code.
Designs time abstractions across the codebase so all timing logic is testable and deterministic.
## currentTime as a measuring tape `currentTime` is a `Long` of virtual milliseconds. It starts at `0` and only moves when delays elapse or you advance the clock. Because it's fully deterministic, you can assert exact durations instead of fuzzy wall-clock ranges. ## Pattern 1 — record-and-diff Capture `currentTime` before and after, then assert the elapsed virtual time. ```kotlin @Test fun backoffGrows() = runTest { val gaps = mutableListOf<Long>() var last = currentTime suspend fun attemptThenWait(delayMs: Long) { delay(delayMs) gaps += currentTime - last last = currentTime } attemptThenWait(100) attemptThenWait(200) attemptThenWait(400) assertEquals(listOf(100L, 200L, 400L), gaps) // exponential backoff proven } ``` ## Pattern 2 — threshold (just-before / just-after) Use `advanceTimeBy` plus `currentTime` to bracket a deadline. ```kotlin val fired = AtomicBoolean(false) launch { delay(5_000); fired.set(true) } advanceTimeBy(4_999); runCurrent() assertFalse(fired.get()) // not yet at t=4999 advanceTimeBy(1); runCurrent() assertTrue(fired.get()) // fired exactly at t=5000 assertEquals(5_000, currentTime) ``` ## Pattern 3 — deadline after draining For a single timed operation, `advanceUntilIdle()` then assert `currentTime` equals the expected completion instant. ## Gotchas - Only `delay`-based timing is virtualized. Code that reads `System.currentTimeMillis()` / `System.nanoTime()` is **not** controlled by `currentTime`; inject a clock abstraction if you need that. - `currentTime` reflects virtual progress, not how long the test really ran. - After manual `advanceTimeBy`, remember the boundary task may need `runCurrent()` before your assertion holds.
- Your code computes backoff from System.nanoTime(). Why does the timing test fail under virtual time?nanoTime reads the real monotonic clock, which the test scheduler doesn't control, so currentTime and the code disagree. Abstract time behind an injectable Clock/TimeSource (or use delay) so tests can drive it.
- How do you prove a withTimeout(5_000) cancels at exactly 5s?Launch the work, advanceTimeBy(4_999)+runCurrent and assert still running, then advance 1 ms; the TimeoutCancellationException fires and currentTime equals 5_000.
saying these in an interview costs you the question
- Asserts on real elapsed wall-clock time
- Expects currentTime to track System.currentTimeMillis
- Adds Thread.sleep to 'wait' for the backoff
- Doesn't reset/record baseline before measuring a diff
- Forgets runCurrent after advanceTimeBy when bracketing a threshold