Android RxJava 2 JUnit test - getMainLooper in android.os.Looper not mocked RuntimeException Android RxJava 2 JUnit test - getMainLooper in android.os.Looper not mocked RuntimeException android android

Android RxJava 2 JUnit test - getMainLooper in android.os.Looper not mocked RuntimeException


This error occurs because the default scheduler returned by AndroidSchedulers.mainThread() is an instance of LooperScheduler and relies on Android dependencies that are not available in JUnit tests.

We can avoid this issue by initializing RxAndroidPlugins with a different Scheduler before the tests are run. You can do this inside of a @BeforeClass method like so:

@BeforeClasspublic static void setUpRxSchedulers() {    Scheduler immediate = new Scheduler() {        @Override        public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {            // this prevents StackOverflowErrors when scheduling with a delay            return super.scheduleDirect(run, 0, unit);        }        @Override        public Worker createWorker() {            return new ExecutorScheduler.ExecutorWorker(Runnable::run);        }    };    RxJavaPlugins.setInitIoSchedulerHandler(scheduler -> immediate);    RxJavaPlugins.setInitComputationSchedulerHandler(scheduler -> immediate);    RxJavaPlugins.setInitNewThreadSchedulerHandler(scheduler -> immediate);    RxJavaPlugins.setInitSingleSchedulerHandler(scheduler -> immediate);    RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> immediate);}

Or you can create a custom TestRule that will allow you to reuse the initialization logic across multiple test classes.

public class RxImmediateSchedulerRule implements TestRule {    private Scheduler immediate = new Scheduler() {        @Override        public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {            // this prevents StackOverflowErrors when scheduling with a delay            return super.scheduleDirect(run, 0, unit);        }        @Override        public Worker createWorker() {            return new ExecutorScheduler.ExecutorWorker(Runnable::run);        }    };    @Override    public Statement apply(final Statement base, Description description) {        return new Statement() {            @Override            public void evaluate() throws Throwable {                RxJavaPlugins.setInitIoSchedulerHandler(scheduler -> immediate);                RxJavaPlugins.setInitComputationSchedulerHandler(scheduler -> immediate);                RxJavaPlugins.setInitNewThreadSchedulerHandler(scheduler -> immediate);                RxJavaPlugins.setInitSingleSchedulerHandler(scheduler -> immediate);                RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> immediate);                try {                    base.evaluate();                } finally {                    RxJavaPlugins.reset();                    RxAndroidPlugins.reset();                }            }        };    }}

Which you can then apply to your test class

public class TestClass {    @ClassRule public static final RxImmediateSchedulerRule schedulers = new RxImmediateSchedulerRule();    @Test    public void testStuff_stuffHappens() {       ...    }}

Both of these methods will ensure that the default schedulers will be overridden before any of the tests execute and before AndroidSchedulers is accessed.

Overriding the RxJava schedulers with an immediate scheduler for unit testing will also make sure the RxJava usages in the code being tested gets run synchronously, which will make it much easier to write the unit tests.

Sources:
https://www.infoq.com/articles/Testing-RxJava2https://medium.com/@peter.tackage/overriding-rxandroid-schedulers-in-rxjava-2-5561b3d14212


I just added

RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline());

in @Before annoted method.


I was getting the same error when testing LiveData. When testing LiveData, this InstantTaskExecutorRule is needed in addition to RxImmediateSchedulerRule if the class being tested has both background thread and LiveData.

@RunWith(MockitoJUnitRunner::class)class MainViewModelTest {    companion object {        @ClassRule @JvmField        val schedulers = RxImmediateSchedulerRule()    }    @Rule    @JvmField    val rule = InstantTaskExecutorRule()    @Mock    lateinit var dataRepository: DataRepository    lateinit var model: MainViewModel    @Before    fun setUp() {      model = MainViewModel(dataRepository)    }    @Test    fun fetchData() {      //given          val returnedItem = createDummyItem()          val observer = mock<Observer<List<Post>>>()          model.getPosts().observeForever(observer)          //when          liveData.value = listOf(returnedItem)          //than          verify(observer).onChanged(listOf(Post(returnedItem.id, returnedItem.title, returnedItem.url)))    }}

Reference:https://pbochenski.pl/blog/07-12-2017-testing_livedata.html