testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0") supplies runTest, which runs on a virtual clock: delay advances it instead of waiting (the body still has 60 real seconds). Inject a StandardTestDispatcher that shares the test's scheduler:
import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
import org.junit.Assert.assertEquals
import org.junit.Test
class CatalogRepository(private val io: CoroutineDispatcher = Dispatchers.IO) {
suspend fun search(q: String) = withContext(io) {
delay(2_000); catalog.filter { q in it.title }.map { it.title } // a slow server
}
}
@OptIn(ExperimentalCoroutinesApi::class)
class CatalogRepositoryTest {
@Test fun searchesRunInVirtualTime() = runTest {
val repo = CatalogRepository(StandardTestDispatcher(testScheduler))
val a = async { repo.search("The") }; val b = async { repo.search("Glass") }
assertEquals(3, (a.await() + b.await()).size)
assertEquals(2_000L, currentTime) // virtual ms: the delays overlapped
}
}Output
... Time: 0.262 OK (1 test)
Four seconds of delays took 0.262 s, and the clock read 2,000 ms because the searches overlapped. StandardTestDispatcher queues coroutines until the test suspends or calls advanceUntilIdle(). For ViewModels, Dispatchers.setMain() swaps in a test dispatcher (ViewModel Tests); for flows, try Cash App's Turbine 2,862 (https://github.com/cashapp/turbine 2,862 ).