TestDispatcher 是什麼#
在 kotlinx-coroutines-test 新版 API 中,TestDispatcher 是測試 coroutine 時最關鍵的元件。它取代了舊版的 TestCoroutineDispatcher,並有兩個主要實作:
- StandardTestDispatcher
- UnconfinedTestDispatcher
兩者都會搭配同一個 TestCoroutineScheduler,讓我們可以控制虛擬時間。
TestCoroutineScheduler#
TestCoroutineScheduler 是負責管理虛擬時間的元件,提供幾個常用 API:
advanceTimeBy(delayMs):把虛擬時間往前推進指定毫秒。advanceUntilIdle():把虛擬時間推到所有排程任務都完成。currentTime:取得目前虛擬時間。runCurrent():執行目前已到期的任務但不前進時間。
範例:
@Test
fun schedulerExample() = runTest {
val results = mutableListOf<String>()
launch {
delay(1000)
results += "1s"
delay(1000)
results += "2s"
}
advanceTimeBy(500)
runCurrent()
assertEquals(emptyList<String>(), results)
advanceTimeBy(600) // 累計 1100ms
runCurrent()
assertEquals(listOf("1s"), results)
advanceUntilIdle()
assertEquals(listOf("1s", "2s"), results)
}StandardTestDispatcher#
StandardTestDispatcher 的特色:
- 啟動 coroutine 後,並不會立即執行內部程式碼。
- 必須由我們手動呼叫
advanceTimeBy、advanceUntilIdle、runCurrent才會推進。 - 適合需要精準驗證 coroutine 執行順序的測試。
範例:
@Test
fun standardExample() = runTest(StandardTestDispatcher()) {
var value = 0
launch {
value = 1
}
// 此時 launch 還沒執行
assertEquals(0, value)
runCurrent()
assertEquals(1, value)
}UnconfinedTestDispatcher#
UnconfinedTestDispatcher 的特色:
- 啟動 coroutine 後,會立即在當前執行緒執行,直到第一個 suspend 點。
- 行為類似一般的
Dispatchers.Unconfined,但仍然支援虛擬時間。 - 適合不需要關心執行順序、想要「直觀同步寫法」的測試。
範例:
@Test
fun unconfinedExample() = runTest(UnconfinedTestDispatcher()) {
var value = 0
launch {
value = 1
}
// 已經執行
assertEquals(1, value)
}兩種 Dispatcher 的選擇#
| 需求 | 建議使用 |
|---|---|
| 需要精準驗證執行順序、時序 | StandardTestDispatcher |
| 想要寫起來像同步程式 | UnconfinedTestDispatcher |
| 預設(不傳入時) | runTest 預設搭配 StandardTestDispatcher |
若測試只是想驗證最終結果(不在乎中間時序),UnconfinedTestDispatcher 通常更省心。
在 runTest 之外使用 TestDispatcher#
有時我們需要把 TestDispatcher 注入到被測元件,例如某個 ViewModel 內部 launch 用的 dispatcher:
@Test
fun viewModelTest() = runTest {
val dispatcher = StandardTestDispatcher(testScheduler)
val viewModel = MyViewModel(dispatcher)
viewModel.load()
advanceUntilIdle()
assertEquals("loaded", viewModel.state)
}重點是:
- 自行建立的 TestDispatcher 要傳入同一個
testScheduler,這樣時間才會同步。 - 如此一來,外層 runTest 的
advanceUntilIdle也能推進內部 dispatcher 的工作。
與舊 API 的對應#
- TestCoroutineDispatcher(舊)➡️ StandardTestDispatcher(新)。
- runBlockingTest 內部(舊)➡️ runTest + TestDispatcher(新)。
- pauseDispatcher / resumeDispatcher(舊)➡️ 改用 advanceTimeBy / runCurrent / advanceUntilIdle 控制。
小結#
- TestDispatcher 是控制 coroutine 測試時序的核心。
- StandardTestDispatcher 需要手動推進,適合精準時序測試。
- UnconfinedTestDispatcher 立即執行,適合驗證最終結果。
- 透過 TestCoroutineScheduler 的 advanceTimeBy、advanceUntilIdle、currentTime,可以在虛擬時間上自由前進。
原文出處#
- 原文/iThome:https://ithelp.ithome.com.tw/articles/10276586