Kotest Coroutines
Testing Coroutine Dispatchers
TestDispatcher is a special CoroutineDispatcher provided by the kotlinx-coroutines-test module that lets developers control a virtual clock and skip delays.
TestDispatcher is a special CoroutineDispatcher provided by the kotlinx-coroutines-test module, which allows developers to control a virtual clock and skip delays.
TestDispatcher supports the following operations.
currentTimegets the current virtual time.runCurrent()runs tasks scheduled at the current virtual time.advanceUntilIdle()runs all queued tasks until there are no more pending tasks.advanceTimeBy(timeDelta)runs queued tasks until the current virtual time has advanced by timeDelta.
To use TestDispatcher in a test, enable coroutineTestScope in the test configuration:
package com.devkuma.kotest.tutorial.coroutines.ex1
import io.kotest.core.spec.style.FunSpec
class TestDispatcherTest : FunSpec() {
init {
test("foo").config(coroutineTestScope = true) {
// this test will run with a test dispatcher
}
}
}
Inside this test, you can retrieve a handle to the scheduler through an extension named testCoroutineScheduler. You can use this scheduler to manipulate time:
import io.kotest.core.test.testCoroutineScheduler
class TestDispatcherTest : FunSpec() {
init {
test("advance time").config(coroutineTestScope = true) {
val duration = 1.days
// launch a coroutine that would normally sleep for 1 day
launch {
delay(duration.inWholeMilliseconds)
}
// move the clock on and the delay in the above coroutine will finish immediately.
testCoroutineScheduler.advanceTimeBy(duration.inWholeMilliseconds)
val currentTime = testCoroutineScheduler.currentTime
}
}
}
You can enable the test dispatcher for all tests in a spec by setting coroutineTestScope to true at the spec level:
class TestDispatcherTest : FunSpec() {
init {
coroutineTestScope = true
test("this test uses a test dispatcher") {
}
test("and so does this test!") {
}
}
}
Finally, you can enable the test dispatcher for all tests in a module by using ProjectConfig:
class ProjectConfig : AbstractProjectConfig() {
override var coroutineTestScope = true
}
Coroutine Debugging
kotlinx-coroutines-debug is a module that provides debugging support for coroutines on the JVM. When enabled, a debug agent is installed by ByteBuddy and captures information when coroutines are created, started, suspended, and resumed.
Kotest provides a feature for enabling debugging per test. You can use it by enabling coroutineDebugProbe in the test configuration.
When this feature is enabled, all coroutines started inside the test are included in a coroutine dump after the test completes or as soon as an exception occurs.
class CoroutineDebugging : FunSpec() {
init {
test("foo").config(coroutineDebugProbes = true) {
someMethodThatLaunchesACoroutine() // launches a new coroutine
}
}
}
The coroutine dump looks like this:
Coroutines dump 2021/11/27 22:17:43
Coroutine DeferredCoroutine{Active}@71f1906, state: CREATED
(Coroutine creation stacktrace)
at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:122)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:82)
at kotlinx.coroutines.BuildersKt.async$default(Unknown Source)
at com.sksamuel.kotest.engine.coroutines.Wibble$1.invokeSuspend(CoroutineDebugTest.kt:37)
at com.sksamuel.kotest.engine.coroutines.Wibble$1.invoke(CoroutineDebugTest.kt)
Spec-level Configuration
You can enable coroutine debugging for all tests in a spec by overriding the coroutineDebugProbes setting inside the spec:
class CoroutineDebugging : FunSpec() {
init {
coroutineDebugProbes = true
test("foo") {
// debugging enabled here
}
test("bar") {
// debugging enabled here
}
}
}
Project-wide Configuration
You can enable coroutine debugging for all tests in a project by using ProjectConfig:
class ProjectConfig : AbstractProjectConfig() {
override val coroutineDebugProbes = true
}