runTest

Testing Coroutines with runTest

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:

CatalogRepositoryTest.kt: two slow searches in virtual timeKotlin
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 ).