When working with Kotlin coroutines in unit tests, developers often encounter the dreaded TimeoutCancellationException or TestCoroutineDispatcher issues. These problems usually occur when a coroutine never finishes within the specified timeout, causing your tests to fail unexpectedly. In this article, we will break down why Kotlin Coroutine Test TimeoutException happens, explore common causes, and walk through practical solutions with complete code examples.

1. Understanding Kotlin Coroutine Timeout in Tests
The TimeoutCancellationException
happens when the coroutine under test takes longer than expected or gets stuck waiting for a job that never completes. In coroutine testing, time is controlled artificially, so improper usage can cause tests to hang indefinitely.
For example:
import kotlinx.coroutines.* import kotlinx.coroutines.test.runTest import kotlin.test.Test class TimeoutExampleTest { @Test fun sampleTestWithTimeout() = runTest { withTimeout(1000) { // Timeout in milliseconds delay(2000) // This will trigger TimeoutCancellationException println("This line will not be reached") } } }
In this code, delay(2000)
exceeds the 1-second timeout, resulting in an exception. While this is expected, similar errors can occur unintentionally if your coroutine logic is flawed.
2. Common Causes of TimeoutException in Coroutine Tests
- Using
delay()
without advancing virtual time in tests - Infinite loops or uncompleted jobs
- Network calls without proper mocking
- Missing
advanceUntilIdle()
oradvanceTimeBy()
in unit tests - Improper usage of
Dispatchers.Main
in tests without setting a test dispatcher
3. Setting Up Coroutine Test Environment Properly
Kotlin’s kotlinx.coroutines.test
library provides tools to control coroutine execution during tests. This helps you run coroutines deterministically without unexpected delays.
Install the dependency in your build.gradle
:
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0"
3.1 Example of Correct Coroutine Test Setup
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.test.* import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @OptIn(ExperimentalCoroutinesApi::class) class CoroutineTestExample { private val testDispatcher = StandardTestDispatcher() @BeforeTest fun setup() { Dispatchers.setMain(testDispatcher) } @AfterTest fun tearDown() { Dispatchers.resetMain() } @Test fun testCoroutineWithoutTimeoutFailure() = runTest { launch { delay(1000) // Simulated async work println("Finished work") } advanceUntilIdle() // Runs all queued coroutines until completion } }
In the example above:
- We replace
Dispatchers.Main
with aStandardTestDispatcher
- We use
advanceUntilIdle()
to avoid TimeoutException - No real delays happen, since the test scheduler advances virtual time instantly
4. Fixing an Actual TimeoutException in a Real Case
Imagine we are testing a repository function that fetches user data from a network API:
class UserRepository(private val api: UserApi) { suspend fun getUserData(): String { delay(2000) // Simulate slow API call return api.fetchUser() } }
Test code without proper handling might fail:
@Test fun testGetUserDataFails() = runTest { val api = mockk<UserApi> { coEvery { fetchUser() } returns "John Doe" } val repo = UserRepository(api) val result = repo.getUserData() // Might cause TimeoutException assertEquals("John Doe", result) }
Why does it fail? The delay(2000)
is real, not virtual. The solution is to advance virtual time:
@Test fun testGetUserDataPasses() = runTest { val api = mockk<UserApi> { coEvery { fetchUser() } returns "John Doe" } val repo = UserRepository(api) val job = launch { val result = repo.getUserData() assertEquals("John Doe", result) } advanceTimeBy(2000) // Skip virtual delay job.join() }
5. Best Practices to Avoid TimeoutException
- Always use
runTest
fromkotlinx.coroutines.test
- Replace
Dispatchers.Main
with a test dispatcher - Use
advanceUntilIdle()
oradvanceTimeBy()
instead of real delays - Mock network and database calls to avoid long waits
- Keep coroutine jobs cancelable to prevent infinite waits
6. Conclusion
Kotlin Coroutine Test TimeoutException is a common pitfall for developers writing coroutine-based tests. By understanding virtual time, properly setting up test dispatchers, and advancing time manually, you can write deterministic, fast, and reliable coroutine tests.
For more details on coroutine testing, check the official documentation: Kotlin Coroutines Test Guide.