Date and time
Tests run in virtual time: a fixed timescale which is controlled by the developer.
The current virtual time can be accessed using the time.nowMillis
property.
To control the current date as proper datetime types, or to inject a controllable clock into other services, see Using the virtual time.
Delay-skipping
Time-sensitive algorithms are often difficult to test, because we want tests to execute quickly, which is often incompatible. For example, testing that a notification is sent a month before disabling an account, or that a ping is sent every 10 seconds.
Through the use of the virtual time, we can control how the algorithm thinks the time is passing, allowing us to trigger these cases easily. However, a major risk of direct time control is that we risk changing the order of events, which might mean that our test doesn't represent the real world anymore.
To decrease these risks, the time is directly controlled by delay
calls.
For example, in this example:
test("Observing the passing of time") {
println("Hello world!")
delay(1000)
println("…After one second")
}
0
ms, and only advances through delay
calls, we know that at the end of this test it is always equal to exactly 1
second.
This determinism allows us to precisely control algorithms that could otherwise be nondeterministic in real time:
test("Deterministic example") {
launchInBackground { //(1)!
while (isActive) {
delay(10)
println("10ms have passed")
}
}
println("Test start")
delay(25)
println("Middle of the test")
delay(10)
println("Test end")
}
launchInBackground
starts a background service that is killed at the end of the test. See asynchronous helpers.
This test is guaranteed to always result in the following trace:
As we can see, the execution can be deterministically tested. Systems that wait for a very long time can be tested as well, since the entire waiting time can be skipped. Complex edge cases and event orderings can be triggered easily.
Warning
Delay-skipping only works in specific dispatchers. The coroutine scope in which tests are executed, as well as the ones created by the asynchronous helpers are already configured with delay-skipping.
However, other dispatchers, most notably Dispatchers.IO
and Dispatchers.Main
, do not use delay-skipping. If calling delay
within a withContext(Dispatchers.IO)
call, the delay
will wait in real time.
This is one of the reasons why real code should never refer to specific dispatchers directly. Instead, services that must start coroutines should accept a CoroutineScope
or CoroutineContext
parameter. Test code can initialize them with:
- If the service accepts a
CoroutineScope
, see the asynchronous helpers section. - If the service accepts a
CoroutineContext
, seetime.scheduler
.
Interaction with fixtures
Prepared values run in the context of each test. All time-control features are available within prepared values.
Shared values are only executed once. The time
accessor is not available within them. We do not recommend using delay
within them, since it will only affect the virtual time of the very first test to run, which is not deterministic (it will be different if you enable parallel execution or if you run a single test).
Interaction with asynchronous operations
Foreground and background operations affect the virtual time exactly in the same way as accessing it directly within a test's body.
Measuring the passing of time
It is possible to measure how long an operation takes in virtual time or in real time.
Measuring in virtual time
See time.source
:
test("Measure how long an operation takes in virtual time") {
val duration = time.source.measureTime {
delay(3.minutes)
}
check(duration == 3.minutes)
}
Measuring how long an operation takes in virtual time is rarely useful: since virtual time only advances naturally when delay
is called, this is equivalent to counting all calls to delay
.
Measuring in real time
To measure how long an operation takes in real time, see measureTime
:
import kotlin.time.measureTime
test("Measure how long an operation takes in real time") {
val duration = measureTime {
delay(3.minutes)
}
check(duration < 10.milliseconds) // because 'delay' is skipped
}
Using the virtual date and time
Often, time.nowMillis
is insufficient: we want to trigger algorithms at specific dates in time, for example to check that an algorithm behaves correctly even at midnight on New Year's. Prepared offers compatibility modules to generate datetime objects from popular libraries.
Accessing the current time
time.now
can be used to access the current time as an Instant
:
time.nowJava
can be used to access the current time as an Instant
:
Setting the time at the start of a test
Injecting the virtual time into the system-under-test
Algorithms we want to test need to have access to the virtual time. Traditionally, this is done by accepting a Clock
in the algorithm's constructor. We can generate such a clock with time.clock
:
Algorithms we want to test need to have access to the virtual time. Traditionally, this is done by accepting a Clock
in the algorithm's constructor. We can generate such a clock with time.clockJava
:
Waiting until a specific time
Instead of using delay
to wait for a specific duration, we can also use delayUntil
to wait until a specific instant.
Instead of using delay
to wait for a specific duration, we can also use delayUntil
to wait until a specific instant.