私はdelay()
を使用するKotlinコルーチンを単体テストしようとしています。単体テストの場合、delay()
は気にしません。テストが遅くなるだけです。 delay()
が呼び出されたときに実際に遅延しない方法でテストを実行したいと思います。
CommonPoolに委任するカスタムコンテキストを使用してコルーチンを実行してみました。
_class TestUiContext : CoroutineDispatcher(), Delay {
suspend override fun delay(time: Long, unit: TimeUnit) {
// I'd like it to call this
}
override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
// but instead it calls this
}
override fun dispatch(context: CoroutineContext, block: Runnable) {
CommonPool.dispatch(context, block)
}
}
_
コンテキストのdelay()
メソッドから戻るだけでいいのにと思っていましたが、代わりにscheduleResumeAfterDelay()
メソッドを呼び出しており、それをデフォルトのスケジューラーに委任する方法がわかりません。
Kotlinx.coroutines v1.2.1では、 kotlinx-coroutines-test モジュールが追加されました。 runBlockingTest
コルーチンビルダー、およびTestCoroutineScope
とTestCoroutineDispatcher
が含まれています。これらは自動進行時間を可能にするだけでなく、delay
を使用してコルーチンをテストする時間を明示的に制御します。
遅延を望まない場合は、スケジュールコールの継続を単純に再開してみませんか。
_class TestUiContext : CoroutineDispatcher(), Delay {
override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
continuation.resume(Unit)
}
override fun dispatch(context: CoroutineContext, block: Runnable) {
//CommonPool.dispatch(context, block) // dispatch on CommonPool
block.run() // dispatch on calling thread
}
}
_
そうすれば、delay()
は遅延なく再開されます。これはまだ遅延して中断するため、他のコルーチンは引き続き実行できます(yield()
など)。
_@Test
fun `test with delay`() {
runBlocking(TestUiContext()) {
launch { println("launched") }
println("start")
delay(5000)
println("stop")
}
}
_
遅延なく実行され、印刷されます。
_start
launched
stop
_
編集:
dispatch
関数をカスタマイズして、継続が実行される場所を制御できます。
Kotlinx.coroutines v0.23.0で TestCoroutineContext が導入されました。
プロ:delay
を使用してコルーチンを真にテストできます。 CoroutineContextの仮想クロックを特定の時間に設定して、予想される動作を確認できます。
欠点:コルーチンコードでdelay
を使用せず、呼び出しスレッドで同期的に実行するだけの場合、@ bj0の回答のTestUiContext
よりも使用するのが少し面倒です(コルーチンを実行するには、TestCoroutineContextでtriggerActions()
を呼び出す必要があります)。
補足:TestCoroutineContext
はkotlinx-coroutines-test
モジュールはコルーチンバージョン1.2.1以降で、非推奨とマークされるか、このバージョンより上のバージョンの標準コルーチンライブラリには存在しません。