【Android】Coroutineのテストについて

Bitrise上でよくコルーチンのテストが失敗することがあり、その解消にあたってコルーチンのテスト周りを理解する必要がありました。

runTest

コルーチンのテスト実行するにはrunTestメソッドを利用します。

Kotlin
runTest {
    // TestScope
}

runTestはコルーチンスコープを1つ生成します。

このTestScope内部ではもちろんコンテキストを保持しています

Kotlin
TestScope
 ├ Job
 ├ TestDispatcher
 └ TestScheduler

そのためrunTest自体はDispatcherだけでなくSchedulerも管理している形となります。

前回記事で話した内容になるのですが、Schedulerはコルーチンをいつどのタイミングで実行させるのかなどを管理しています。

runTest = このTestSchedulerに管理されているテストが対象

viewModelScopleを利用するとテストが不安定になりやすい

Kotlin
runTest Scope
   └ launch
       └ withContext

上記ではテストスコープ直下でlaunchして、withContextを実行しています。この場合はlaunch時はディスパッチャの指定が無いのでTestScopeに紐づくコンテキストの情報が利用されるため問題ないのです。

んで、AndroidのviewModelScopeがどこから来ているかというと…

Kotlin
CoroutineScope(
   SupervisorJob() +
   Dispatchers.Main.immediate
)

viewModelScopeは独立したJob+独立したDispatcherから作られています。もうこの時点でテスト管理下から外れてしまうのです。

コルーチンの親子関係はJobで決まるため、viewModelScope.launchした場合は独立したものとして扱われます。

Kotlin
runTest
   Job-A
   
viewModelScope
   Job-B
   
別です

また、それを避けるためにUnconfinedTestDispatcherを利用していた場合はMainDispatcherはTestDispatcherになりますが、viewModelScopeのJobは独立したままの状態です。

Schedulerは共有できるけどJobは非共有という中途半端な状態。

UnconfinedTestDispatcherの特性として即座に実行されますがsuspend後はどのスレッドで再開されるか分からないので、これが原因でテスト開始前に別のテストによるエラーなどでrunTest開始時に失敗するということが起きます。

UnconfinedTestDispatcherではSchedulerに管理されることなく即実行され、再開時もいつになるか不明のためadvanceUntilIdleが効かなくなる