So, I am currently trying to test some time sensitive code in my Android app:
- Inside my
MainPresenter I use interval() and delay() to do a countdown and display text in the View accordingly. Both, by default, use Schedulers.computation().
- For testing, I'd like to use a
TestScheduler so that I am able to forward the time and check if the the correct methods are called on a mock of the View.
- Therefore I am using
RxJavaPlugins and RxJavaSchedulersHook to override the computation Scheduler to use my TestScheduler instead. I also keep a reference to the latter in my test class and use that in each unit test.
See this code, which almost works:
public class MainPresenterTest {
private TestScheduler testScheduler;
@Before
public void setupSchedulers() {
testScheduler = new TestScheduler();
RxJavaPlugins.getInstance().registerSchedulersHook(new RxJavaSchedulersHook() {
@Override
public Scheduler getComputationScheduler() {
return testScheduler;
}
@Override
public Scheduler getIOScheduler() {
return testScheduler;
}
@Override
public Scheduler getNewThreadScheduler() {
return testScheduler;
}
});
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
@Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
}
@Test
public void nothingHappensInTheFirstThreeSeconds() {
MainPresenter.View mockedMainPresenterView = mock(MainPresenter.View.class);
MainPresenter mainPresenter = new MainPresenter(mockedMainPresenterView);
mainPresenter.onResume();
verifyZeroInteractions(mockedMainPresenterView);
testScheduler.advanceTimeTo(2_999, TimeUnit.MILLISECONDS);
verifyZeroInteractions(mockedMainPresenterView);
}
@Test
public void theWholeSequenceCompletesAfterEightSeconds() {
MainPresenter.View mockedMainPresenterView = mock(MainPresenter.View.class);
MainPresenter mainPresenter = new MainPresenter(mockedMainPresenterView);
mainPresenter.onResume();
verifyZeroInteractions(mockedMainPresenterView);
testScheduler.advanceTimeTo(8, TimeUnit.SECONDS);
verify(mockedMainPresenterView).displayText("5");
verify(mockedMainPresenterView).displayText("4");
verify(mockedMainPresenterView).displayText("3");
verify(mockedMainPresenterView).displayText("2");
verify(mockedMainPresenterView).displayText("1");
verify(mockedMainPresenterView).displayText("Hello World!");
verifyNoMoreInteractions(mockedMainPresenterView);
}
Actually, it works fine as long as there is only one test case. If I add another one, I get the following exception when @Before is running for the second time:
java.lang.IllegalStateException: Another strategy was already registered: ...
I have now done some research and found several candidates for a solution:
If you were relying on the ability to change the main thread scheduler over time (such as for tests), return a delegating scheduler from the hook which allows changing the delegate instance at will.
I am assuming this method would also work for any other RxJava scheduler - if I knew how to do it.
So, which of these (or any other) methods is preferred for resetting/changing the TestScheduler that's used for overriding Schedulers.computation() in between tests? In particular, how would the third alternative work exactly? How would that "delegating scheduler" look or work?
Thanks for any advice!
So, I am currently trying to test some time sensitive code in my Android app:
MainPresenterI useinterval()anddelay()to do a countdown and display text in theViewaccordingly. Both, by default, useSchedulers.computation().TestSchedulerso that I am able to forward the time and check if the the correct methods are called on a mock of theView.RxJavaPluginsandRxJavaSchedulersHookto override thecomputationScheduler to use myTestSchedulerinstead. I also keep a reference to the latter in my test class and use that in each unit test.See this code, which almost works:
Actually, it works fine as long as there is only one test case. If I add another one, I get the following exception when
@Beforeis running for the second time:I have now done some research and found several candidates for a solution:
RxJavaPlugins.getInstance().reset();and a customTestRule. See also this discussion about makingreset()publicI am assuming this method would also work for any other RxJava scheduler - if I knew how to do it.
So, which of these (or any other) methods is preferred for resetting/changing the
TestSchedulerthat's used for overridingSchedulers.computation()in between tests? In particular, how would the third alternative work exactly? How would that "delegating scheduler" look or work?Thanks for any advice!