UnconfinedTestDispatcher

Creates an instance of an unconfined TestDispatcher.

This dispatcher is similar to Dispatchers.Unconfined: the tasks that it executes are not confined to any particular thread and form an event loop; it's different in that it skips delays, as all TestDispatchers do.

Like Dispatchers.Unconfined, this one does not provide guarantees about the execution order when several coroutines are queued in this dispatcher. However, we ensure that the launch and async blocks at the top level of runTest are entered eagerly. This allows launching child coroutines and not calling runCurrent for them to start executing.

@Test
fun testEagerlyEnteringChildCoroutines() = runTest(UnconfinedTestDispatcher()) {
var entered = false
val deferred = CompletableDeferred<Unit>()
var completed = false
launch {
entered = true
deferred.await()
completed = true
}
assertTrue(entered) // `entered = true` already executed.
assertFalse(completed) // however, the child coroutine then suspended, so it is enqueued.
deferred.complete(Unit) // resume the coroutine.
assertTrue(completed) // now the child coroutine is immediately completed.
}

Using this TestDispatcher can greatly simplify writing tests where it's not important which thread is used when and in which order the queued coroutines are executed. Another typical use case for this dispatcher is launching child coroutines that are resumed immediately, without going through a dispatch; this can be helpful for testing Channel and StateFlow usages.

@Test
fun testUnconfinedDispatcher() = runTest {
val values = mutableListOf<Int>()
val stateFlow = MutableStateFlow(0)
val job = launch(UnconfinedTestDispatcher(testScheduler)) {
stateFlow.collect {
values.add(it)
}
}
stateFlow.value = 1
stateFlow.value = 2
stateFlow.value = 3
job.cancel()
// each assignment will immediately resume the collecting child coroutine,
// so no values will be skipped.
assertEquals(listOf(0, 1, 2, 3), values)
}

Please be aware that, like Dispatchers.Unconfined, this is a specific dispatcher with execution order guarantees that are unusual and not shared by most other dispatchers, so it can only be used reliably for testing functionality, not the specific order of actions. See Dispatchers.Unconfined for a discussion of the execution order guarantees.

In order to support delay skipping, this dispatcher is linked to a TestCoroutineScheduler, which is used to control the virtual time and can be shared among many test dispatchers. If no scheduler is passed as an argument, Dispatchers.Main is checked, and if it was mocked with a TestDispatcher via Dispatchers.setMain, the TestDispatcher.scheduler of the mock dispatcher is used; if Dispatchers.Main is not mocked with a TestDispatcher, a new TestCoroutineScheduler is created.

Additionally, name can be set to distinguish each dispatcher instance when debugging.

See also

for a more predictable TestDispatcher.