fresh digitable

めんどくさかったなってことを振り返ったり振り返らなかったりするための記録

CoroutineLiveDataをどうにかしてテストする

関数androidx.lifecycle.livedataからCoroutineLiveDataというLiveDataを取得できる(CoroutineLiveDatainternalクラスなので実際見えるのはLiveData )。例えば次のような感じで中断関数を渡してやるとライフサイクルとかとの関係をいい感じにやってくれつつemitSource()で渡したLiveDataをセットしてくれる(普通の値の場合はemit()を使う)。

val itemsSource: LiveData<List<Item>> = livedata {
  val source: LiveData<List<Item>> = dao.getItemsSource()
  emitSource(source)
  val items: List<Item> = restApi.fetchItems()
  dao.insert(items)
}

今回はこのLiveDataの値(=中断関数のロジック)に関するテストを書くことを考える。

書いたコードなど

関係ありそうな依存関係:

    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3"
    implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:2.2.0"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"

    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.3'
    testImplementation "androidx.arch.core:core-testing:2.1.0"
    testImplementation 'org.robolectric:robolectric:4.3.1'

なにをやっているか

  • CoroutineLiveDataは内部でLooperを使うらしいのでJVMテストではこれをモックしなければならない。そのためにRobolectricが必要。
  • LiveDataをテストでobserveするためにはInstantTaskExecutorRuleが必要なのでarch.core-testingパッケージを入れている。
  • テストのスレッドとテスト対象のスレッドとを同じにしたいのでテストのDispatcherを注入する。TestCoroutineDispatcherというやつがあるのでこれを使う。テスト対象ではこのDispatcherlivedata()関数に渡す。
  • CoroutineLiveDataをobserveしたあとでRobolectricのshadowOf(getMainLooper()).idle()を呼べば全部の処理が流れるので、テストコードでアサーションできるようになる。めでたし。
  • と思ったけど、テスト対象側(livedata()の中)で起動したコルーチンで起きた例外をテスト側では検知できない。なのでDispatcherと一緒にCoroutineExceptionHandlerも注入してやる必要がある。今回はライブラリに入っていたTestCoroutineExceptionHandlerを使っている。