LoginSignup
5
1

More than 5 years have passed since last update.

RxJava/RxAndroidの非同期テストで「 java.lang.ExceptionInInitializerError 」が発生した時に…

Posted at

java.lang.ExceptionInInitializerError

RxJava/RxAndroidの非同期テストを書いていたときに、「 java.lang.ExceptionInInitializerError 」というエラーが発生しました。

java.lang.ExceptionInInitializerError
    at io.reactivex.android.schedulers.AndroidSchedulers$1.call(AndroidSchedulers.java:35)
    at io.reactivex.android.schedulers.AndroidSchedulers$1.call(AndroidSchedulers.java:33)
    at io.reactivex.android.plugins.RxAndroidPlugins.callRequireNonNull(RxAndroidPlugins.java:70)
    at io.reactivex.android.plugins.RxAndroidPlugins.initMainThreadScheduler(RxAndroidPlugins.java:40)
  Caused by: java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details.
    at android.os.Looper.getMainLooper(Looper.java)
    at io.reactivex.android.schedulers.AndroidSchedulers$MainHolder.<clinit>(AndroidSchedulers.java:29)
    ... 41 more

原因

皆さんよく書くと思われる .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) の部分で
AndroidSchedulers.mainThread() がAndroidに依存したクラスを利用しているから、とのことです。

:bow: 参考 :bow:
https://stackoverflow.com/questions/43356314/android-rxjava-2-junit-test-getmainlooper-in-android-os-looper-not-mocked-runt
https://qiita.com/ko2ic/items/397fd147d4f3d0bdca09

対策

RxJavaPlugins/RxAndroidPluginsを使用する。

下記のような処理をテストの @Before などに書くと、Schedulerを指定することが出来、エラーを回避できました。

val immediate = object : Scheduler() {
    override fun scheduleDirect(run: Runnable, delay: Long, unit: TimeUnit): Disposable {
        return super.scheduleDirect(run, 0, unit)
    }

    override fun createWorker(): Scheduler.Worker {
        return ExecutorScheduler.ExecutorWorker(Executor { it.run() })
    }
}
RxJavaPlugins.setInitIoSchedulerHandler { immediate }
RxJavaPlugins.setInitComputationSchedulerHandler { immediate }
RxJavaPlugins.setInitNewThreadSchedulerHandler { immediate }
RxJavaPlugins.setInitSingleSchedulerHandler { immediate }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { immediate }

さらなる問題

これで大丈夫と思いきや、さらなる問題が…

非同期処理が成功したあとに、続けて非同期処理を行うと、2回目の非同期処理が永遠に終わらない状態に。
(この対策とは関係のない部分が原因の可能性も否定できませんが…)

対策(2)

テスト時だけ、
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.trampoline()).observeOn(Schedulers.trampoline()) に変更。

Schedulerを変更させちゃえばいい、という発想ですね。

DroidKaigiのアプリを見てみると、Repositoryが SchedulerProvider をInjectするようになっており、テスト時には TestSchedulerProvider というのを注入する形になっていました。
https://github.com/DroidKaigi/conference-app-2018/blob/master/app/src/main/java/io/github/droidkaigi/confsched2018/data/repository/ContributorDataRepository.kt#L19

確かにRxJavaPlugins/RxAndroidPluginsで変更させるのは若干の黒魔術感があるので、DIで変えてあげるほうがいいのかもしれないと思いました。

結論

・Schedulerを変更させる必要があります。
・DIでSchedulerを注入させて、テスト時は Schedulers.trampoline() にする、のがいいかもしれない。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1