Testing coroutines requires special handling to manage time and control execution flow. Learn how to use runTest, Turbine, and TestDispatcher for robust async tests.
Basic Coroutine Testing with runTest
The runTest builder automatically advances virtual time and manages dispatcher scope:
@Test
fun testFlowEmission() = runTest {
val flow = flowOf(1, 2, 3)
val results = mutableListOf<Int>()
flow.collect { results.add(it) }
assertEquals(listOf(1, 2, 3), results)
}
Testing Delayed Emissions with advanceTimeBy
Control virtual time progression to test delays:
@Test
fun testDelayedEmission() = runTest {
val flow = flow {
emit(1)
delay(1000)
emit(2)
}
val results = mutableListOf<Int>()
launch {
flow.collect { results.add(it) }
}
advanceTimeBy(500)
assertEquals(listOf(1), results)
advanceUntilIdle()
assertEquals(listOf(1, 2), results)
}
Advanced Testing with Turbine
Turbine simplifies Flow testing with expressive assertions:
@Test
fun testFlowWithTurbine() = runTest {
val viewModel = MyViewModel()
viewModel.uiState.test {
awaitItem().apply {
assertEquals("Loading", status)
}
advanceUntilIdle()
awaitItem().apply {
assertEquals("Success", status)
}
expectNoEvents()
}
}
Custom TestDispatcher Setup
Create scoped test dispatchers for complex scenarios:
val testDispatcher = StandardTestDispatcher()
val testScope = TestScope(testDispatcher)
@Test
fun testWithCustomDispatcher() {
testScope.runTest {
val job = launch {
delay(100)
println("Done")
}
advanceUntilIdle()
job.join()
}
}
Proper coroutine testing ensures reliable asynchronous code. Combine these tools to handle all timing scenarios effectively.
8 Android app templates on Gumroad
Top comments (0)