skip to content

How can you make code that uses measureTime / measureTimedValue deterministic in tests, and what role does a custom TimeSource play?

level: seniorimportance: should knowfreq 25%

answer

  1. Top-level measureTime = real clock = flaky tests
  2. TimeSource.measureTime { } overloads bound to a source
  3. Inject TimeSource (default Monotonic) for testability
  4. TestTimeSource advanced with += duration
  5. Coroutines: runTest virtual time

basics

~20 s

The 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 s

The 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 lines
kotlin
import 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

for a junior

Recognizes that timing real code in tests is flaky.

for a middle

Knows TestTimeSource exists and is advanced manually with +=.

for a senior

Designs the TimeSource as an injected seam and uses the source-bound measureTime overloads for deterministic tests.

for a principal

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

context