tezvyn:

Testing Coroutines: Control Time with `runTest`

AI-drafted, machine-checkedSource: developer.android.comadvanced
Testing Coroutines: Control Time with `runTest`

Test asynchronous coroutine code as if it were synchronous. The `kotlinx-coroutines-test` library lets you control a virtual clock. Use `runTest` to wrap tests calling suspend functions, ensuring your test waits for async work before asserting results.

WHY IT EXISTS: Coroutines run concurrently, often on different threads, and can be suspended. Standard unit tests run sequentially and finish immediately. This mismatch means a test might end before the coroutine it's testing, leading to flaky or incorrect test results.

THE MENTAL MODEL: Think of the kotlinx-coroutines-test library as a time machine for your code. Instead of waiting for real-world time to pass, which is slow and flaky, you use a TestDispatcher to control a virtual clock. The runTest block puts you inside this time machine, letting you execute and assert on asynchronous operations as if they were simple, synchronous code.

HOW IT WORKS: The core is the runTest function. It creates a special test coroutine scope that uses a TestDispatcher. This dispatcher doesn't use real threads for concurrency but instead manages a queue of pending tasks on a single test thread. runTest automatically advances this virtual clock, ensuring any coroutines launched within the test block are guaranteed to have completed before the test block finishes and assertions are run.

WHEN TO USE IT: Use runTest for any unit test that interacts with code using coroutines. This is standard practice for testing ViewModels, Repositories, or any business logic that performs asynchronous operations like network requests or database access. It's essential for creating fast, reliable, and deterministic tests for your concurrent code.

WHEN NOT TO USE IT: For simple, non-suspending functions, you don't need runTest. For integration or end-to-end tests running on a real device, you will be dealing with real dispatchers and real delays, though injecting test dispatchers can still be a useful strategy.

ONE CANONICAL EXAMPLE: To test a ViewModel that uses viewModelScope.launch to fetch data, you must replace the main dispatcher. A common pattern is a JUnit rule that calls Dispatchers.setMain(testDispatcher) before the test and Dispatchers.resetMain() after. Then, inside your @Test function wrapped with runTest, you can call the ViewModel's method and assert on its state, confident that the background work has completed.

Read the original → developer.android.com

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.