liveBook · Manning

Test coroutine scheduler

As explained in Kotlin in Action, 2E

A TestCoroutineScheduler is a scheduler that tracks and controls coroutine execution and virtual time in coroutine tests.

A TestCoroutineScheduler tracks coroutine execution and virtual time during tests. Through runTest, it fast-forwards delays instead of waiting in real time, making tests that include long delays run almost instantly. section 18.5.1

Advance virtual time deliberately

In runTest, one coroutine delays for 500 milliseconds and another for one second. Calling delay(600.milliseconds) moves the virtual clock to 600 and runs only the first coroutine; another 500 milliseconds completes the second. Use runCurrent() to execute work ready at the current time, then advanceUntilIdle() to run all scheduled work, including delayed tasks. section 18.5.1

Keep these scheduler behaviors in mind when writing assertions:

  • runTest uses a single-threaded test dispatcher, so child coroutines are concurrent but do not run in parallel with the test body by default. section 18.5.1
  • Assertions may run before launched coroutines unless the test reaches a suspension point such as delay or yield, or explicitly advances the scheduler. section 18.5.1
Listing 18.12 Forwarding the virtual clock with delay
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testDelay() = runTest {
   var x = 0
   launch {
       delay(500.milliseconds)
       x++
   }
   launch {
       delay(1.second)
       x++
   }
   println(currentTime) // 0

   delay(600.milliseconds)
   assertEquals(1, x)
   println(currentTime) // 600

   delay(500.milliseconds)
   assertEquals(2, x)
   println(currentTime) // 1100
}

Trace virtual-time advancement while delayed coroutines update shared state. section 18.5.1

Listing 18.13 Running all scheduled coroutines via advanceUntilIdle
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testDelay() = runTest {
   var x = 0
   launch {
       x++
       launch {
           x++
       }
   }
   launch {
       delay(200.milliseconds)
       x++
   }
   runCurrent()
   assertEquals(2, x)
   advanceUntilIdle()
   assertEquals(3, x)
}

Compare runCurrent with advanceUntilIdle when nested and delayed coroutines are scheduled. section 18.5.1

How Kotlin in Action, 2E covers it

How deep it goes
Explains virtual time, single-threaded scheduling, suspension points, and controls such as runCurrent and advanceUntilIdle.
Examples
Demonstrates delayed increments, current-time inspection, runCurrent, and advanceUntilIdle with nested coroutines.
What it assumes
Assumes coroutine and suspension-point basics while teaching virtual time and scheduler control through runTest.
What you can do after
Operate runTest tests by advancing virtual time and scheduling work before making assertions.
How it connects
Connects TestCoroutineScheduler with runTest, test dispatchers, TestScope, virtual time, suspension points, and coroutine assertions.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Kotlin in Action, 2E. The section links point to the parts of the book it draws on.