RxJavaを使った非同期処理のコードをテストしたいときに、そのままだとエラーになったのでその対処法メモです。
結論
テスト時のみSchedulers.trampoline()
を使う。
環境
RxJava 2.1.4
RxAndroid 2.0.1
Kotlin 1.1.50
Mockito 2.10.0
エラー内容
java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked.
Scheduler
まずSchedulerのinterfaceを用意
interface BaseSchedulerProvider {
fun computation(): Scheduler
fun io(): Scheduler
fun ui(): Scheduler
}
BaseSchedulerProviderを継承したScheduler(実装で利用)
object SchedulerProvider : BaseSchedulerProvider {
override fun computation(): Scheduler = Schedulers.computation()
override fun io(): Scheduler = Schedulers.io()
override fun ui(): Scheduler = AndroidSchedulers.mainThread()
}
BaseSchedulerProviderを継承したScheduler(テストで利用)
全てSchedulers.trampoline()
を返すようにしている。
class ImmediateSchedulerProvider : BaseSchedulerProvider {
override fun computation(): Scheduler = Schedulers.trampoline()
override fun io(): Scheduler = Schedulers.trampoline()
override fun ui(): Scheduler = Schedulers.trampoline()
}
実装
MVPアーキテクチャのPresenterでRxJavaでリクエストしてるところ。(説明のために簡略化してます)
mSchedulerProvider
はBaseSchedulerProvider
型のメンバ変数。
実装時はSchedulerにSchedulerProvider
を利用する。
mRepository.getList()
.subscribeOn(mSchedulerProvider.io())
.observeOn(mSchedulerProvider.ui())
.subscribe({
mView.showList(it)
}, {
mView.showError()
})
テスト
Repository#getList()
の戻り値をMockiot.when()
でモックして、それぞれのパターンに応じてViewの特定のメソッド(今回はshowList()
とshowError()
のみ)が呼ばれているかをMockito.verify()
でテストしている。
テスト時はSchedulerにImmediateSchedulerProvider
を利用する。
@Test
fun loadListOk() {
`when`(getList()).thenReturn(Single.just(mList))
mPresenter.loadList()
verify(mView).showList(mItemListWithFooter)
}
@Test
fun loadListEmpty() {
`when`(getList()).thenReturn(Single.just(arrayListOf()))
mPresenter.loadList()
verify(mView).showError()
}
@Test
fun loadListError() {
`when`(getList()).thenReturn(Single.error(Exception()))
mPresenter.loadList()
verify(mView).showError()
}
まとめ
Schedulers.trampoline()
を使うことでRxJavaを使った非同期処理のところもテストできます。
実際に使うときはDIライブラリ(Daggerなど)でSchedulerを差し替えるようにするのが良さそうです。
補足
RxAndroidPluginのregisterSchedulersHook()
でgetMainThreadScheduler()
をOverrideすることでAndroidSchedulers.mainThread()
が返すSchedulerを差し替えることもできるっぽいです。
参考
googlesamples/android-architecture at todo-mvp-rxjava
https://github.com/googlesamples/android-architecture/tree/todo-mvp-rxjava/
RxAndroidPlugins (rxandroid 1.2.1 API)
https://static.javadoc.io/io.reactivex/rxandroid/1.2.1/rx/android/plugins/RxAndroidPlugins.html
unit testing - Android RxJava 2 JUnit test - getMainLooper in android.os.Looper not mocked RuntimeException - Stack Overflow
https://stackoverflow.com/questions/43356314/android-rxjava-2-junit-test-getmainlooper-in-android-os-looper-not-mocked-runt