Coroutine Testing
Test suspend functions.
Coroutine Testing is a free Kotlin Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Why Coroutine Testing is Special
Coroutines run asynchronously and may use delays. Tests need controlled time and a way to wait for suspend functions to finish.
The kotlinx-coroutines-test library provides exactly this.
Adding the Test Library
Add the coroutines test artifact to your test dependencies.
dependencies {
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1")
}runTest
runTest is the entry point. It runs a coroutine test body and automatically waits for all child coroutines to complete.
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class FetchTest {
@Test
fun fetches() = runTest {
// suspend calls allowed here
}
}Virtual Time
Inside runTest, delay calls are skipped using virtual time. A 10-second delay completes instantly while preserving ordering.
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
// runTest {
// delay(10_000) // returns immediately
// }Advancing Time Manually
The test scheduler lets you advance time deliberately with advanceTimeBy and run pending work with runCurrent.
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
// runTest {
// launch { delay(1000); println("done") }
// advanceTimeBy(1000)
// runCurrent()
// }Test Dispatchers
StandardTestDispatcher queues coroutines until you advance time. UnconfinedTestDispatcher runs them eagerly. Choose based on the control you need.
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.UnconfinedTestDispatcher
val standard = StandardTestDispatcher()
val unconfined = UnconfinedTestDispatcher()Injecting a Dispatcher
Production code should accept a dispatcher rather than hardcoding Dispatchers.IO. Tests then inject a test dispatcher.
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class Service(private val io: CoroutineDispatcher = Dispatchers.IO) {
suspend fun load(): String = withContext(io) { "data" }
}Replacing Dispatchers.Main
For Android-style code, swap the Main dispatcher with Dispatchers.setMain(testDispatcher) and reset it afterward.
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.setMain
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.StandardTestDispatcher
// Dispatchers.setMain(StandardTestDispatcher())
// ... test ...
// Dispatchers.resetMain()Testing a Suspend Function
A suspend function can be called and asserted directly inside runTest.
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
suspend fun compute(): Int { delay(1000); return 42 }
// @Test fun computes() = runTest {
// assertEquals(42, compute())
// }Testing Flows
Collect a Flow and assert emitted values. The Turbine library makes flow testing especially clean.
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
// @Test fun emits() = runTest {
// val values = flowOf(1, 2, 3).toList()
// assertEquals(listOf(1, 2, 3), values)
// }A Runnable Coroutine Snippet
Outside tests, runBlocking bridges suspend code to a normal main. This illustrates the suspend function being tested.
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.delay
suspend fun compute(): Int { delay(100); return 42 }
fun main() = runBlocking {
println(compute())
}Quick Check
A suspend function calls delay(5000). Why does a test using runTest finish almost instantly?
Recap
You can test coroutines reliably:
runTestruns suspend code and waits for children- Virtual time skips delays; advance manually when needed
- Inject dispatchers; use
setMain/resetMain - Test flows by collecting (or with Turbine)
That completes the Testing course.
Frequently asked questions
Is the “Coroutine Testing” lesson free?
Yes — the full text of “Coroutine Testing” is free to read here on the web, and the Kotlin Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Kotlin Academy course, upgrade to CoddyKit PRO.
What will I learn in “Coroutine Testing”?
Test suspend functions. You practise Kotlin Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Kotlin Academy?
No prior experience is required. Kotlin Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Coroutine Testing” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Kotlin Academy lesson?
Yes. Every Kotlin Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Writing Tests with JUnit5
- Assertions
- Mocking with MockK
- Coroutine Testing