現象
普通に作っていると AndroidSchedulers.mainThread()
の部分で ExceptionInInitializerError
になります。
何故エラーになるのか
これは、AndroidSchedulers.mainThread()
によって返されるスケジューラが、Androidに依存しているためです。(Androidに依存していると何もしなければ、エミュレーターを使いAndroidTestで実行する必要があります。)
このリンク先が該当のソースになります。
解決方法
単純にテストが実行される前に、RxAndroidPlugins機構により別のSchedulerとして初期化することで、この問題を回避できます。
具体的なソース
以下kotlinのソースです。単純にPlugin機構を使って、利用するSchedulerをAndroidに依存しないようにしているだけのTestRuleを定義します。
RxImmediateSchedulerRule.kt
class RxImmediateSchedulerRule : TestRule {
private val immediate = object : Scheduler() {
override fun scheduleDirect(run: Runnable, delay: Long, unit: TimeUnit): Disposable {
return super.scheduleDirect(run, 0, unit)
}
override fun createWorker(): Worker {
return ExecutorScheduler.ExecutorWorker { it.run() }
}
}
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
RxJavaPlugins.setInitIoSchedulerHandler({ _ -> immediate })
RxJavaPlugins.setInitComputationSchedulerHandler({ _ -> immediate })
RxJavaPlugins.setInitNewThreadSchedulerHandler({ _ -> immediate })
RxJavaPlugins.setInitSingleSchedulerHandler({ _ -> immediate })
RxAndroidPlugins.setInitMainThreadSchedulerHandler({ _ -> immediate })
try {
base.evaluate()
} finally {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
}
}
}
}
}
そして、これをテストの前に実行するだけです。ちなみに@ClassRule
を使わない理由は、こちらの記事を参照してください。
@RunWith(PowerMockRunner::class)
@PrepareForTest(Auth::class)
class LoginViewModelTest {
@Rule
val schedulers: RxImmediateSchedulerRule = RxImmediateSchedulerRule()
// companion object {
// @JvmField
// @ClassRule
// val schedulers: RxImmediateSchedulerRule = RxImmediateSchedulerRule()
// }
@Test
fun onClickLogin() {
val mockAuth = PowerMockito.mock(Auth::class.java)
val target = LoginViewModel(mockAuth)
target.mail.set("email")
target.password.set("password")
val result = Single.just(AuthEntity().apply {
accessToken = "123456"
userId = 100
})
PowerMockito.`when`(mockAuth.login("email", "password")).thenReturn(result)
target.onClickLogin().run()
Mockito.verify(mockAuth).login("email", "password")
}
}