How can you make code that uses measureTime / measureTimedValue deterministic in tests, and what role does a custom TimeSource play?
answer
- Top-level measureTime = real clock = flaky tests
- TimeSource.measureTime { } overloads bound to a source
- Inject TimeSource (default Monotonic) for testability
- TestTimeSource advanced with += duration
- Coroutines: runTest virtual time
basics
~20 sThe plain measureTime always uses the real clock, so it is hard to test. Instead, call measureTime on a TimeSource you control — like TestTimeSource — and advance it manually, so the measured duration is exactly what you set.
solid answer
~40 sThe free functions measureTime { } and measureTimedValue { } always read the default TimeSource.Monotonic, so they return real elapsed time — non-deterministic and flaky to assert on. kotlin.time also exposes member/extension versions that you call on a specific TimeSource: timeSource.measureTime { } and timeSource.measureTimedValue { }. By passing a TestTimeSource (an AbstractLongTimeSource you can advance with += duration), you fully control the clock: nothing advances unless you call timeSource += 100.milliseconds, so the measured Duration is exactly your scripted value. This is the idiomatic way to unit-test timing-dependent logic deterministically: inject a TimeSource into your class (defaulting to TimeSource.Monotonic in production), use it for measureTime, and substitute a TestTimeSource in tests. For coroutine code, the coroutines test dispatcher's virtual time plays a similar deterministic role.
code
kotlin · 12 linesimport kotlin.time.TestTimeSource
import kotlin.time.measureTime
import kotlin.time.Duration.Companion.milliseconds
import kotlin.test.assertEquals
fun test() {
val ts = TestTimeSource()
val elapsed = ts.measureTime {
ts += 120.milliseconds // scripted elapsed time
}
assertEquals(120.milliseconds, elapsed) // deterministic
}go deeper
Recognizes that timing real code in tests is flaky.
Knows TestTimeSource exists and is advanced manually with +=.
Designs the TimeSource as an injected seam and uses the source-bound measureTime overloads for deterministic tests.
Establishes a project pattern: inject TimeSource everywhere, combine TestTimeSource with runTest virtual time, and ban real-clock assertions in CI.
## The testability problem The top-level `measureTime { }` / `measureTimedValue { }` read the real `TimeSource.Monotonic`. Asserting `assert(elapsed < 5.milliseconds)` is brittle on CI machines under load. You need to control time. ## TimeSource as a seam `TimeSource` is the abstraction behind these helpers. `kotlin.time` provides **member/extension** overloads bound to a specific source: ```kotlin val ts: TimeSource = TimeSource.Monotonic val d = ts.measureTime { work() } // uses ts, not the global default val tv = ts.measureTimedValue { compute() } // TimedValue<T> from ts ``` Design your class to **inject** the source: ```kotlin class RateLimiter(private val timeSource: TimeSource = TimeSource.Monotonic) { fun process(): Duration = timeSource.measureTime { doWork() } } ``` In production you get the real monotonic clock; in tests you pass a fake. ## TestTimeSource Kotlin ships **`TestTimeSource`** (in `kotlin.time`, experimental in some versions), a manually-advanced source. It extends `AbstractLongTimeSource`. It only advances when you tell it to, via `+=`: ```kotlin import kotlin.time.TestTimeSource import kotlin.time.Duration.Companion.milliseconds val ts = TestTimeSource() val mark = ts.markNow() ts += 250.milliseconds // advance the virtual clock println(mark.elapsedNow()) // 250ms, exactly and deterministically ``` With `measureTime`: ```kotlin val ts = TestTimeSource() val d = ts.measureTime { ts += 30.milliseconds // simulate elapsed time inside the block } assertEquals(30.milliseconds, d) // exact, no flakiness ``` ## Coroutines angle For `suspend` code timed inside coroutines, the **`kotlinx-coroutines-test`** dispatcher provides **virtual time**: `runTest { }` auto-advances `delay` instantly and deterministically, complementing `TestTimeSource` for the timing seam. ## Key APIs named - `TimeSource`, `TimeSource.Monotonic` - `TimeSource.measureTime { }`, `TimeSource.measureTimedValue { }` (source-bound overloads) - `TestTimeSource`, `AbstractLongTimeSource`, `timeSource += duration` - `runTest` / virtual time (kotlinx-coroutines-test) ## Takeaway Don't assert on real elapsed time. Inject a `TimeSource`, use its `measureTime`/`measureTimedValue` overloads, and drive a `TestTimeSource` in tests for fully deterministic timing assertions.
- Why can't you make the top-level measureTime deterministic directly?It hard-codes the default TimeSource.Monotonic. You must instead use the source-bound overload on an injected TimeSource you control (e.g., TestTimeSource).
- How do you advance a TestTimeSource?With the plusAssign operator: timeSource += someDuration. It does not advance on its own.
Instead of timing with the real wall stopwatch (which CI jitter messes with), you hand the code a stopwatch whose hands you turn by hand exactly where you want them.
saying these in an interview costs you the question
- Asserting on real elapsed time in unit tests
- Not knowing measureTime has a TimeSource-bound overload
- Thinking TestTimeSource advances automatically
- Mocking System.nanoTime() instead of injecting a TimeSource