Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am encountering a RuntimeException when attempting to run JUnit tests for a presenter that is using observeOn(AndroidSchedulers.mainThread()).

Since they are pure JUnit tests and not Android instrumentation tests, they don't have access to Android dependencies, causing me to encounter the following error when executing the tests:

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)     at io.reactivex.android.schedulers.AndroidSchedulers.<clinit>(AndroidSchedulers.java:32)     … 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)     ...   java.lang.NoClassDefFoundError: Could not initialize class io.reactivex.android.schedulers.AndroidSchedulers     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)     … 
like image 340
starkej2 Avatar asked Apr 11 '17 21:04

starkej2


1 Answers

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:

@BeforeClass public 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-RxJava2 https://medium.com/@peter.tackage/overriding-rxandroid-schedulers-in-rxjava-2-5561b3d14212

like image 150
starkej2 Avatar answered Oct 17 '22 00:10

starkej2