RxJava 2 переопределяет планировщик ввода-вывода в unit test
Я пытаюсь протестировать следующий код RxKotlin/RxJava 2:
validate(data)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap { ... }
Я пытаюсь переопределить планировщики следующим образом:
// Runs before each test suite
RxJavaPlugins.setInitIoSchedulerHandler { Schedulers.trampoline() }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
Однако при запуске теста я получаю следующую ошибку:
java.lang.ExceptionInInitializerError
...
Caused by: java.lang.NullPointerException: Scheduler Callable result can't be null
at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
at io.reactivex.plugins.RxJavaPlugins.applyRequireNonNull(RxJavaPlugins.java:1317)
at io.reactivex.plugins.RxJavaPlugins.initIoScheduler(RxJavaPlugins.java:306)
at io.reactivex.schedulers.Schedulers.<clinit>(Schedulers.java:84)
Кто-нибудь испытал эту проблему?
Тест работал нормально при использовании RxKotlin/RxJava 1 и переопределяет следующий планировщик:
RxAndroidPlugins.getInstance().registerSchedulersHook(object : RxAndroidSchedulersHook() {
override fun getMainThreadScheduler() = Schedulers.immediate()
})
RxJavaPlugins.getInstance().registerSchedulersHook(object : RxJavaSchedulersHook() {
override fun getIOScheduler() = Schedulers.immediate()
})
Спасибо!
Ответы
Ответ 1
Я предлагаю вам использовать другой подход и добавить слой абстракции к вашим планировщикам. У этого парня хорошая статья: https://medium.com/@peter.tackage/an-alternative-to-rxandroidplugins-and-rxjavaplugins-scheduler-injection-9831bbc3dfaf
В Котлине это выглядело бы как-то так:
interface SchedulerProvider {
fun ui(): Scheduler
fun computation(): Scheduler
fun trampoline(): Scheduler
fun newThread(): Scheduler
fun io(): Scheduler }
И затем вы переопределите это с помощью собственной реализации SchedulerProvider:
class AppSchedulerProvider : SchedulerProvider {
override fun ui(): Scheduler {
return AndroidSchedulers.mainThread()
}
override fun computation(): Scheduler {
return Schedulers.computation()
}
override fun trampoline(): Scheduler {
return Schedulers.trampoline()
}
override fun newThread(): Scheduler {
return Schedulers.newThread()
}
override fun io(): Scheduler {
return Schedulers.io()
}
}
И один для тестирования классов:
class TestSchedulerProvider : SchedulerProvider {
override fun ui(): Scheduler {
return Schedulers.trampoline()
}
override fun computation(): Scheduler {
return Schedulers.trampoline()
}
override fun trampoline(): Scheduler {
return Schedulers.trampoline()
}
override fun newThread(): Scheduler {
return Schedulers.trampoline()
}
override fun io(): Scheduler {
return Schedulers.trampoline()
}
}
Ваш код будет выглядеть так, как вы называете RxJava:
mCompositeDisposable.add(mDataManager.getQuote()
.subscribeOn(mSchedulerProvider.io())
.observeOn(mSchedulerProvider.ui())
.subscribe(Consumer<Quote> {
...
И вы просто переопределите свою реализацию SchedulerProvider
на основе того, где вы ее протестируете. Вот пример проекта для ссылки, я связываю тестовый файл, который будет использовать тестируемую версию SchedulerProvider
: https://github.com/Obaied/DingerQuotes/blob/master/app/src/test/java/com/obaied/dingerquotes/QuotePresenterTest.kt#L31
Ответ 2
Подумал! Это связано с тем, что в этом коде:
validate(data)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap { ... }
validate(data)
возвращал Observable
, который испускал следующее: emitter.onNext(null)
. Поскольку RxJava 2 больше не принимает значения null
, flatMap
не вызывался. Я изменил validate
, чтобы вернуть Completable
, и обновил переопределение планировщика на следующее:
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
Теперь тесты проходят!
Ответ 3
Это точный синтаксис, который работал для меня:
RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline())
Ответ 4
В качестве альтернативы предлагаемым решениям, это работало некоторое время в моих проектах. Вы можете использовать его в своих тестовых классах, например так:
@get:Rule
val immediateSchedulersRule = ImmediateSchedulersRule()
И класс выглядит так:
class ImmediateSchedulersRule : ExternalResource() {
val immediateScheduler: Scheduler = object : Scheduler() {
override fun createWorker() = ExecutorScheduler.ExecutorWorker(Executor { it.run() })
// This prevents errors when scheduling a delay
override fun scheduleDirect(run: Runnable, delay: Long, unit: TimeUnit): Disposable {
return super.scheduleDirect(run, 0, unit)
}
}
override fun before() {
RxJavaPlugins.setIoSchedulerHandler { immediateScheduler }
RxJavaPlugins.setComputationSchedulerHandler { immediateScheduler }
RxJavaPlugins.setNewThreadSchedulerHandler { immediateScheduler }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { immediateScheduler }
RxAndroidPlugins.setMainThreadSchedulerHandler { immediateScheduler }
}
override fun after() {
RxJavaPlugins.reset()
}
}
Вы можете найти способ перехода с TestRule на ExternalResource здесь и получить больше информации о тестировании RxJava 2 здесь.