Skip to content

Suiteopensavvy.prepared.suitetime

time


Time control center.

Why?

Often, we need to create algorithms that behave differently when executed at different times. Using the system clock makes code much harder to test, or problems to reproduce. Instead, it is recommended that algorithms take as input a "time generator", so fake implementations can be injected for testing.

When executing the system under test, we need to provide such objects to the algorithm. This attribute, time, is the control center for generating such values and for controlling their outputs.

Delay-skipping

Inside tests, calls to delay are skipped in a way that keeps the order of events. This makes tests much faster to execute without compromising on testing algorithms that need to wait for an event.

This also applies to other time-related coroutine control functions, like withTimeout.

This allows to trivially implement algorithms which require skipping a large amount of time:

test("Data is expired after 6 months") {
    val data = createSomeData()
    assertFalse(data.isExpired())

    delay((6 * 30).days)

    assertTrue(data.isExpired())
}

Assuming all services use either the test clock or the test time source, the entire system will think 6 months have passed, and all started tasks will have run the same number of times, and in the same order, as if 6 months had actually passed.

To learn more about delay skipping, see the KotlinX.Coroutines' documentation: runTest.

The delay-skipping behavior is controlled by Time.scheduler. If you want to create your own coroutines, remember to add the scheduler to their CoroutineContext, or they will delay for real.

Time control

Inside tests, a virtual time is available, that describes how much delay has been skipped. A test always starts at the epoch. delay allows us to move time forwards, executing all tasks as their execution date is reached. We can also control the time directly.

Example

This example checks that an event was recorded at the expected time:

test("The event should be recorded at the current time") {
    val start = time.nowMillis
    val result = foo()
    val end = time.nowMillis

    assert(result.timestamp start)
    assert(result.timestamp < end)
}

See also