Bitrise上でよくコルーチンのテストが失敗することがあり、その解消にあたってコルーチンのテスト周りを理解する必要がありました。
目次
runTest
コルーチンのテスト実行するにはrunTestメソッドを利用します。
runTest {
// TestScope
}runTestはコルーチンスコープを1つ生成します。
このTestScope内部ではもちろんコンテキストを保持しています
TestScope
├ Job
├ TestDispatcher
└ TestSchedulerそのためrunTest自体はDispatcherだけでなくSchedulerも管理している形となります。
前回記事で話した内容になるのですが、Schedulerはコルーチンをいつどのタイミングで実行させるのかなどを管理しています。
runTest = このTestSchedulerに管理されているテストが対象
viewModelScopleを利用するとテストが不安定になりやすい
runTest Scope
└ launch
└ withContext上記ではテストスコープ直下でlaunchして、withContextを実行しています。この場合はlaunch時はディスパッチャの指定が無いのでTestScopeに紐づくコンテキストの情報が利用されるため問題ないのです。
んで、AndroidのviewModelScopeがどこから来ているかというと…
CoroutineScope(
SupervisorJob() +
Dispatchers.Main.immediate
)viewModelScopeは独立したJob+独立したDispatcherから作られています。もうこの時点でテスト管理下から外れてしまうのです。
コルーチンの親子関係はJobで決まるため、viewModelScope.launchした場合は独立したものとして扱われます。
runTest
Job-A
viewModelScope
Job-B
別ですまた、それを避けるためにUnconfinedTestDispatcherを利用していた場合はMainDispatcherはTestDispatcherになりますが、viewModelScopeのJobは独立したままの状態です。
Schedulerは共有できるけどJobは非共有という中途半端な状態。
UnconfinedTestDispatcherの特性として即座に実行されますがsuspend後はどのスレッドで再開されるか分からないので、これが原因でテスト開始前に別のテストによるエラーなどでrunTest開始時に失敗するということが起きます。
UnconfinedTestDispatcherではSchedulerに管理されることなく即実行され、再開時もいつになるか不明のためadvanceUntilIdleが効かなくなる