sky’s 雑記

主にAndroidとサーバーサイドの技術について記事を書きます

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. とのこと。

Replace TimeoutCancellationException with TimeoutException · Issue #1374 · Kotlin/kotlinx.coroutines · GitHub

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]

www.youtube.com

[3]

www.youtube.com

[4] kotlinx.coroutines/kotlinx-coroutines-test at master · Kotlin/kotlinx.coroutines · GitHub