kotlinx-coroutines-testのrunBlockingTestについて
前回に引き続きCoroutinesの単体テストの話です。
kotlinx-coroutines-testのDispatchers.setMainについて - sky’s 雑記
今回はrunBlockingTest[1]について取り扱いたいと思います。
⚠理解が曖昧な状態で記述している可能性があります、間違いがあれば訂正お願いします。
環境
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.20"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9"
runBlockingTestとは
kotlinx-coroutines-testに含まれるAPIでrunBlocking同様に現在のスレッドをブロックしてコルーチンを起動することが可能。
上記例ではrunBlockingに渡しているブロックで2度コルーチンが起動されておりそれぞれで10secディレイを入れているため以下のような出力になる。
次にrunBlockingをrunBlockingTestに変更した場合
delayが即時実行され処理が完了していることがわかる。
runBlockingTestの用途
runBlockingTestが導入された背景はTesting Coroutines on Android (Android Dev Summit '19)[2]とKotlinConf 2019: Testing with Coroutines by Sean McQuillan[3]が詳しい。Android Dev Summitでは良い単体テストは速く(fast)、信頼性があり(reliable)、独立している(isolated)と言われており、runBlockingTestは特に速さと信頼性の観点で導入されたように思う。
ではrunBlockingTestを利用したいのはどういう場合だろうか。
普段ViewModelが呼び出すRepositoryのsuspend関数は実行結果をmockすることが多くあまりピンと来なかったが、KotlinConfのプレゼンテーションではsuspend関数のタイムアウト処理をテストするケースが例として挙げられていた。例えば以下のようなタイムアウトが設定されたsuspend関数があった場合、これをrunBlockingで実行すると5秒待つ必要がある。
一方同じ処理をrunBlockingTestで書くと以下のようになり即座にテストが通る、またDelayControllerによりCoroutineDispatcherのvirtual timeを変更することでtimeOutが絡むテストが容易に記述できる。
補足
ここまでrunBlockingTestの用途について書いてみたが現状ExperimentalCoroutinesApiであることに加えて、いくつかクリティカルに思えるissueが上がっていたため導入については要検討という印象である。続報に期待したい。
AbstractMethodError when use withTimeout
AbstractMethodError when use withTimeout · Issue #2307 · Kotlin/kotlinx.coroutines · GitHub
2021/01/18時点で最新版1.4.2のkotlinx-coroutines-testを利用すると発生するエラー
Provided example test for withTimeout fails
Provided example test for withTimeout fails · Issue #1390 · Kotlin/kotlinx.coroutines · GitHub
ReadMeのExample[5]をそのまま書くと発生するエラー
Replace TimeoutCancellationException with TimeoutException
上述のsuspend fun foo
でTimeoutExceptionをそのままthrowせずにtry/catchしてFooExceptionを投げ直している理由。TimeoutCancellationException is CancellationException, thus is never reported.
とのこと。
Use Kotlin Coroutines in your Android App
Use Kotlin Coroutines in your Android App | Android Developers
runBlockingTest is experimental, and currently has a bug that makes it fail the test if a coroutine switches to a dispatcher that executes a coroutine on another thread. The final stable is not expected to have this bug.
参考
[1] runBlockingTest - kotlinx-coroutines-test
[2]
[3]
[4] kotlinx.coroutines/kotlinx-coroutines-test at master · Kotlin/kotlinx.coroutines · GitHub