ReactiveX / RxJava

RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.
Apache License 2.0
47.87k stars 7.6k forks source link

2.x: Scheduler Callable result can't be null, setInitIoSchedulerHandler with trampoline #5193

Closed darekdeo closed 7 years ago

darekdeo commented 7 years ago

RxJava version is 2.0.1

Not sure if it's a bug or my lack of knowledge. The issue I have happens when I try to return trampoline scheduler instead of io for my unit tests. An error occurs:

java.lang.ExceptionInInitializerError
    at com.dariuszdeoniziak.charades.presenters.CategoryListPresenter.loadCategories(CategoryListPresenter.java:53)
    at com.dariuszdeoniziak.charades.presenters.CategoryListPresenterTest.loadCategoriesCallsShowCategories(CategoryListPresenterTest.java:44)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
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:989)
    at io.reactivex.plugins.RxJavaPlugins.initIoScheduler(RxJavaPlugins.java:213)
    at io.reactivex.schedulers.Schedulers.<clinit>(Schedulers.java:79)
    ... 31 more

Process finished with exit code 255

The loadCategories code is as follows, the exception is thrown at .subscribeOn(Schedulers.io()):

Single<List<Category>> categoriesSingle = Single.fromCallable(new Callable<List<Category>>() {

        @Override
        public List<Category> call() throws Exception {
            return modelInteractor.getCategories();
        }
    });

public void loadCategories() {
        view.showProgressIndicator();
        categoriesSingle
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new SingleObserver<List<Category>>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onSuccess(List<Category> value) {
                        if (value.isEmpty())
                            view.showEmptyList();
                        else
                            view.showCategories(value);
                    }

                    @Override
                    public void onError(Throwable e) {
                        view.showEmptyList();
                    }
                });
    }

Test just calls loadCategories method and mocks few things:

@Test
public void loadCategoriesCallsShowCategories() {
    when(presenter.modelInteractor.getCategories())
            .thenReturn(categories);
    presenter.loadCategories();
    verify(modelInteractor).getCategories();
    verify(view).showProgressIndicator();
    verify(view).showCategories(categories);
}

Last but not least, the place where I setInitIoSchedulerHandler is custom TestRunner:

package com.dariuszdeoniziak.charades.utils;

import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.InitializationError;

import java.util.concurrent.Callable;

import io.reactivex.Scheduler;
import io.reactivex.android.plugins.RxAndroidPlugins;
import io.reactivex.functions.Function;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;

public class RxJavaTestRunner extends BlockJUnit4ClassRunner {

    public RxJavaTestRunner(Class<?> testClass) throws InitializationError {
        super(testClass);

        RxAndroidPlugins.reset();
        RxAndroidPlugins.setInitMainThreadSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
            @Override
            public Scheduler apply(Callable<Scheduler> schedulerCallable) throws Exception {
                return Schedulers.trampoline();
            }
        });

        RxJavaPlugins.reset();
        RxJavaPlugins.setInitNewThreadSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
            @Override
            public Scheduler apply(Callable<Scheduler> schedulerCallable) throws Exception {
                return Schedulers.trampoline();
            }
        });
        RxJavaPlugins.setInitIoSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
            @Override
            public Scheduler apply(Callable<Scheduler> schedulerCallable) throws Exception {
                return Schedulers.trampoline();
            }
        });
    }
}

Is it possible to setInitIoSchedulerHandler as above? Full project is available on my git: https://github.com/darekdeo/charades I have currently replaced Schedulers.io with Schedulers.newThread on git, which works fine.

akarnokd commented 7 years ago

No. When you call setInitX you can't access the Schedulers from within because the callback itself executes on the Schedulers' class initialization and other Schedulers fields may not be ready. Use the plain setIoSchedulerHandler to override and later clear the custom scheduler.

darekdeo commented 7 years ago

It works, thank you. This was fast.