0Pricing
Kotlin Academy · Lesson

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:

  • runTest runs 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

  1. Writing Tests with JUnit5
  2. Assertions
  3. Mocking with MockK
  4. Coroutine Testing
← Back to Kotlin Academy