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
Android: Listview.Getscrolly() - Does It Work
What Does Fragmentmanager and Fragmenttransaction Exactly Do
Android M Write to Sd Card - Permission Denied
Security "Crypto" Provider Deprecated in Android N
Android.App.Application Cannot Be Instantiated Due to Nullpointerexception
How to Perform Minification and Obfuscation with the Jack Compiler
Android Stock Browser Not Respecting CSS Overflow
Google Maps API V2 Class Not Found
How to Add a Bullet Symbol in Textview
Find the Physical Address of Exception Vector Table from Kernel Module
Flutter: How to Fix "A Renderflex Overflowed by Pixels " Error
Right Align Text in Android Textview
How to Create a Release Android Library Package (Aar) in Android Studio (Not Debug)
Android Webview Jellybean -> Should Not Happen: No Rect-Based-Test Nodes Found
Onitemclicklistener Was Not Work with the Checkbox
Getting Error "Gradle Dsl Method Not Found: 'Compile()'" When Syncing Build.Gradle