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

AndroidUnit TestingJunitRx JavaRx Java2

Android Problem Overview


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)
	…

Android Solutions


Solution 1 - Android

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

Solution 2 - Android

I just added

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

in @Before annoted method.

Solution 3 - Android

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

Solution 4 - Android

Based on @starkej2 answer, with some changes, the correct answer for Kotlin developers would be:

  1. Create RxImmediateSchedulerRule.kt class:

,

import io.reactivex.Scheduler
import io.reactivex.android.plugins.RxAndroidPlugins
import io.reactivex.internal.schedulers.ExecutorScheduler
import io.reactivex.plugins.RxJavaPlugins
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import java.util.concurrent.Executor

class RxImmediateSchedulerRule : TestRule {
    private val immediate = object : Scheduler() {
        override fun createWorker(): Worker {
            return ExecutorScheduler.ExecutorWorker(Executor { 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()
                }
            }
        }
    }
}

2. On your test class, create schedulers ClassRule:

    class TestViewModelTest {

    companion object {
       @ClassRule
       @JvmField
       val schedulers = RxImmediateSchedulerRule()
    }

    @Before
    fun setUp() {
        //your setup code here
    }

    @Test
    fun yourTestMethodHere{}
    }

Solution 5 - Android

As in the advice in this Medium article by Peter Tackage you can inject the Schedulers yourself.

We all know that directly calling static methods can make for classes that are hard to test and if you use a dependency injection framework like Dagger 2 injecting the Schedulers can be especially easy. The example is as follows:

Define an interface in your project:

public interface SchedulerProvider {
    Scheduler ui();
    Scheduler computation();
    Scheduler io();
    Scheduler special();
    // Other schedulers as required…
}

Define an implementation:

final class AppSchedulerProvider implements SchedulerProvider {
    @Override 
    public Scheduler ui() {
        return AndroidSchedulers.mainThread();
    }
    @Override 
    public Scheduler computation() {
        return Schedulers.computation();
    }
    @Override 
    public Scheduler io() {
        return Schedulers.io();
    }
    @Override 
    public Scheduler special() {
        return MyOwnSchedulers.special();
    }
}

Now instead of using direct references to the Schedulers like this:

 bookstoreModel.getFavoriteBook()
               .map(Book::getTitle)
               .delay(5, TimeUnit.SECONDS)
               .observeOn(AndroidSchedulers.mainThread())
               .subscribe(view::setBookTitle));

You use references to your interface:

bookstoreModel.getFavoriteBook()
          .map(Book::getTitle)
          .delay(5, TimeUnit.SECONDS, 
                 this.schedulerProvider.computation())
          .observeOn(this.schedulerProvider.ui())
          .subscribe(view::setBookTitle));

Now for your tests, you could define a TestSchedulersProvider like this:

public final class TestSchedulersProvider implements SchedulerProvider {

      @Override
      public Scheduler ui() {
          return new TestScheduler();
      }

      @Override
      public Scheduler io() {
          return Schedulers.trampoline(); //or test scheduler if you want
      }

      //etc
}

You now have all of the advantages of using TestScheduler when you want to in your unit tests. This comes in handy for situations where you might want to test a delay:

@Test
public void testIntegerOneIsEmittedAt20Seconds() {
    //arrange
    TestObserver<Integer> o = delayedRepository.delayedInt()
            .test();

    //act
    testScheduler.advanceTimeTo(20, TimeUnit.SECONDS);

    //assert
    o.assertValue(1);
}

Otherwise, if you don't want to use injected Schedulers the static hooks mentioned in the other methods can be done using lambdas:

@Before
public void setUp() {
    RxAndroidPlugins.setInitMainThreadSchedulerHandler(h -> Schedulers.trampoline());
    RxJavaPlugins.setIoSchedulerHandler(h -> Schedulers.trampoline());
//etc
}

Solution 6 - Android

If you still have problem and none of above codes help you, besides that, it is not a bad idea to add this line to your app.gradle file:

testOptions {
    animationsDisabled = true
    unitTests {
        includeAndroidResources = true
        returnDefaultValues = true
    }
}

Solution 7 - Android

Just to add to starkej2's answer, it worked very well for me until I ran into stackoverflowerror when testing an Observable.timer(). There's no help on that but luckily I got it working with the below Scheduler definition, with all other tests also passing.

new Scheduler() {
            @Override
            public Worker createWorker() {
                return new ExecutorScheduler.ExecutorWorker(new ScheduledThreadPoolExecutor(1) {
                    @Override
                    public void execute(@NonNull Runnable runnable) {
                        runnable.run();
                    }
                });
            }
        };

Rest as in starkej2's answer. Hope this helps someone.

Solution 8 - Android

I had this issue and came to this post, but I couldn't find anything for RX 1. So this is the solution if you have the same problem on the first version.

@BeforeClass
public static void setupClass() {
    RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
        @Override
        public Scheduler getMainThreadScheduler() {
            return Schedulers.trampoline();
        }
    });
}

Solution 9 - Android

For those who are doing with Kotlin and using the Rule instead of creating a companion object you can use @get:Rule.

So instead of using :

companion object {
 @ClassRule
 @JvmField
 val schedulers = RxImmediateSchedulerRule()
}

You can simply use :

@get:Rule
val schedulers = RxImmediateSchedulerRule()

Solution 10 - Android

For RxJava 1 you can create different schedulers like this:

 @Before
 public void setUp() throws Exception {
    // Override RxJava schedulers
    RxJavaHooks.setOnIOScheduler(new Func1<Scheduler, Scheduler>() {
        @Override
        public Scheduler call(Scheduler scheduler) {
            return Schedulers.immediate();
        }
    });

    RxJavaHooks.setOnComputationScheduler(new Func1<Scheduler, Scheduler>() {
        @Override
        public Scheduler call(Scheduler scheduler) {
            return Schedulers.immediate();
        }
    });

    RxJavaHooks.setOnNewThreadScheduler(new Func1<Scheduler, Scheduler>() {
        @Override
        public Scheduler call(Scheduler scheduler) {
            return Schedulers.immediate();
        }
    });

    // Override RxAndroid schedulers
    final RxAndroidPlugins rxAndroidPlugins = RxAndroidPlugins.getInstance();
    rxAndroidPlugins.registerSchedulersHook(new RxAndroidSchedulersHook() {
        @Override
        public Scheduler getMainThreadScheduler() {
            return Schedulers.immediate();
    }
});
} 

@After
public void tearDown() throws Exception {
RxJavaHooks.reset();
RxAndroidPlugins.getInstance().reset();
}

https://stackoverflow.com/questions/39827081/unit-testing-android-application-with-retrofit-and-rxjava/39828581#39828581

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionstarkej2View Question on Stackoverflow
Solution 1 - Androidstarkej2View Answer on Stackoverflow
Solution 2 - AndroidjknairView Answer on Stackoverflow
Solution 3 - Androids-hunterView Answer on Stackoverflow
Solution 4 - AndroidGentView Answer on Stackoverflow
Solution 5 - AndroidDavid RawsonView Answer on Stackoverflow
Solution 6 - AndroidFarrokh ShahriariView Answer on Stackoverflow
Solution 7 - AndroidAA_PVView Answer on Stackoverflow
Solution 8 - Androidsunlover3View Answer on Stackoverflow
Solution 9 - AndroidSkizo-ozᴉʞSView Answer on Stackoverflow
Solution 10 - AndroidhiteshView Answer on Stackoverflow