Android Rxjava 2 Junit Test - Getmainlooper in Android.Os.Looper Not Mocked Runtimeexception

Method getMainLooper in android.os.Looper not mocked still occuring even after adding RxImmediateSchedulerRule

As you update LiveData value, you should add @get:Rule var rule: TestRule = InstantTaskExecutorRule() also.

Don't forget to add following to build.gradle file:

dependencies {
// ...
testImplementation "androidx.arch.core:core-testing:2.1.0"
}

Also, change your test code accordingly to avoid NullPointerException:

@Test
fun testWithNetwork() {
trendingViewModel.isConnected = true
Mockito.`when`(trendingRepository.fetchTrendingRepos()).thenReturn(Single.just(listOf<TrendingRepo>()))
trendingViewModel.fetchTrendingRepos()
verify(trendingRepository, times(1)).getTrendingRepos()
}

Mockito.when() lets you to do different actions every time your mock method is called. If you don't use it, you may see possible NullPointerException depending on your test function.

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:

@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

Android testing RxJava 2

You should not use AndroidSchedulers.mainThead() for testing purpose. You can use Schedulers.trampoline() instead. It basically executes all the tasks on the current thread without any queueing and the timed overloads use blocking sleep as well.

You can use injection framework (as Dagger 2) for injecting the right scheduler. Or simply you can add this in your test:

@BeforeClass
public static void setupTest() {
RxAndroidPlugins.setInitMainThreadSchedulerHandler(
__ -> Schedulers.trampoline());
}


Related Topics



Leave a reply



Submit