I am writing a unit test and need to mock an Observable (from retrofit)
The code in the tested component is as follows:
getApiRequestObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(...)
In the unit test (against the JVM so AndroidSchedulers are not available) I need to make it all synchronous so my tests will look like:
#Test
public void testSomething() {
doReturn(mockedResponse).when(presenter).getApiRequestObservable();
presenter.callApi();
verify(object,times(1)).someMethod();
}
To do this, I should register hooks in a setUp() method:
#Before
public void setUp() throws Exception {
// AndroidSchedulers.mainThread() is not available here so we fake it with this hook
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
// We want synchronous operations
RxJavaPlugins.getInstance().registerSchedulersHook(new RxJavaSchedulersHook(){
#Override
public Scheduler getIOScheduler() {
return Schedulers.immediate();
}
});
}
But this throws the above exception as I am apparently not allowed to register two hooks. Is there any way around that?
The problem is that you're not resetting test state - you can verify that by running single test. To fix your particular problem you need to reset rx plugins state like so:
#Before
public void setUp(){
RxJavaPlugins.getInstance().reset();
RxAndroidPlugins.getInstance().reset();
//continue setup
...
}
You can even wrap the reset into a reusable #Rule as described by Alexis Mas blog post:
public class RxJavaResetRule implements TestRule {
#Override
public Statement apply(Statement base, Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
//before: plugins reset, execution and schedulers hook defined
RxJavaPlugins.getInstance().reset();
RxAndroidPlugins.getInstance().reset();
// register custom schedulers
...
base.evaluate();
}
};
}
}
Related
I have a bit problem setting up proper unit tests for my interactor classes in my android app. These classes is where I have "business logic" of my app.
Here is one such class:
public class ChangeUserPasswordInteractor {
private final FirebaseAuthRepositoryType firebaseAuthRepositoryType;
public ChangeUserPasswordInteractor(FirebaseAuthRepositoryType firebaseAuthRepositoryType) {
this.firebaseAuthRepositoryType = firebaseAuthRepositoryType;
}
public Completable changeUserPassword(String newPassword){
return firebaseAuthRepositoryType.getCurrentUser()
.flatMapCompletable(firebaseUser -> {
firebaseAuthRepositoryType.changeUserPassword(firebaseUser, newPassword);
return Completable.complete();
})
.observeOn(AndroidSchedulers.mainThread());
}
}
Here is a test I wrote:
#RunWith(JUnit4.class)
public class ChangeUserPasswordInteractorTest {
#Mock
FirebaseAuthRepositoryType firebaseAuthRepositoryType;
#Mock
FirebaseUser firebaseUser;
#InjectMocks
ChangeUserPasswordInteractor changeUserPasswordInteractor;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
RxAndroidPlugins.reset();
RxAndroidPlugins.setInitMainThreadSchedulerHandler(schedulerCallable -> Schedulers.trampoline());
}
#Test
public void changeUserPassword() {
Mockito.when(firebaseAuthRepositoryType.getCurrentUser()).thenReturn(Observable.just(firebaseUser));
Mockito.when(firebaseAuthRepositoryType.changeUserPassword(firebaseUser, "test123")).thenReturn(Completable.complete());
changeUserPasswordInteractor.changeUserPassword("test123")
.test()
.assertSubscribed()
.assertNoErrors()
.assertComplete();
}
}
Problem here I am having is that this test completes with no errors, even If I change the password from "test123" on changeUserPassword invokation to something else, or if I in the mock return "Completable.onError(new Throwable())".
I can't understand this behavior. Any suggestions how to set up the test?
The last line of your flatMapCompletable always returns Completable.complete()
it should be :
firebaseAuthRepositoryType.changeUserPassword(firebaseUser, newPassword);
so :
public Completable changeUserPassword(String newPassword){
return firebaseAuthRepositoryType.getCurrentUser()
.flatMapCompletable(firebaseUser ->
firebaseAuthRepositoryType.changeUserPassword(firebaseUser, newPassword));
}
I am newly learned the RxAndroid but unfortunately the book I studied did not covered any unit test. I have searched a lot on google but failed to find any simple tutorial that cover the RxAndroid unit test in precise way.
I have basically wrote a small REST API using RxAndroid and Retrofit 2. Here is the ApiManager class:
public class MyAPIManager {
private final MyService myService;
public MyAPIManager() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
// set your desired log level
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder b = new OkHttpClient.Builder();
b.readTimeout(35000, TimeUnit.MILLISECONDS);
b.connectTimeout(35000, TimeUnit.MILLISECONDS);
b.addInterceptor(logging);
OkHttpClient client = b.build();
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://192.168.1.7:8000")
.client(client)
.build();
myService = retrofit.create(MyService.class);
}
public Observable<Token> getToken(String username, String password) {
return myService.getToken(username, password)
.subscribeOn(Schedulers.io());
.observeOn(AndroidSchedulers.mainThread());
}
}
I am trying to create a unit test for getToken. Here is my sample test:
public class MyAPIManagerTest {
private MyAPIManager myAPIManager;
#Test
public void getToken() throws Exception {
myAPIManager = new MyAPIManager();
Observable<Token> o = myAPIManager.getToken("hello", "mytoken");
o.test().assertSubscribed();
o.test().assertValueCount(1);
}
}
Due to subscribeOn(Schedulers.io) the above test does not run on main thread due to which it returns 0 value. If I remove subscribeOn(Schedulers.io) from MyAPIManager then it run well and return 1 value. Is there any way to test with Schedulers.io?
Great question and certainly one topic that is lacking a lot of coverage in the community. I would like to share a couple of solutions I personally used and were splendid. These are thought for RxJava 2 but they're available with RxJava 1 just under different names. You will for sure find it if you need it.
RxPlugins and RxAndroidPlugins (this is my favourite so far)
So Rx actually provides a mechanism to change the schedulers provided by the static methods inside Schedulers and AndroidSchedulers. These are for example:
RxJavaPlugins.setComputationSchedulerHandler
RxJavaPlugins.setIoSchedulerHandler
RxJavaPlugins.setNewThreadSchedulerHandler
RxJavaPlugins.setSingleSchedulerHandler
RxAndroidPlugins.setInitMainThreadSchedulerHandler
What these do is very simple. They make sure that when you call i.e. Schedulers.io() the returned scheduler is the one you provide in the handler set in setIoSchedulerHandler. Which scheduler do you want to use? Well you want Schedulers.trampoline(). This means that the code will run on the same thread as it was before. If all schedulers are in the trampoline scheduler, then all will be running on the JUnit thread. After the tests are run, you can just clean the whole thing by calling:
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
I think the best approach to this is to use a JUnit rule. Here's a possible one (sorry for the kotlin syntax):
class TrampolineSchedulerRule : TestRule {
private val scheduler by lazy { Schedulers.trampoline() }
override fun apply(base: Statement?, description: Description?): Statement =
object : Statement() {
override fun evaluate() {
try {
RxJavaPlugins.setComputationSchedulerHandler { scheduler }
RxJavaPlugins.setIoSchedulerHandler { scheduler }
RxJavaPlugins.setNewThreadSchedulerHandler { scheduler }
RxJavaPlugins.setSingleSchedulerHandler { scheduler }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler }
base?.evaluate()
} finally {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
}
}
}
}
At the top of your unit test you just need to declare a public attribute annotated with #Rule and instantiated with this class:
#Rule
public TrampolineSchedulerRule rule = new TrampolineSchedulerRule()
in kotlin
#get:Rule
val rule = TrampolineSchedulerRule()
Injecting schedulers (a.k.a. dependency injection)
Another possibility is to inject the schedulers in your classes so at test time you can inject again the Schedulers.trampoline() and in your app you can inject the normal schedulers. This might work for a while, but it will soon become cumbersome when you need to inject loads of schedulers just for a simple class. Here's one way of doing this
public class MyAPIManager {
private final MyService myService;
private final Scheduler io;
private final Scheduler mainThread;
public MyAPIManager(Scheduler io, Scheduler mainThread) {
// initialise everything
this.io = io;
this.mainThread = mainThread;
}
public Observable<Token> getToken(String username, String password) {
return myService.getToken(username, password)
.subscribeOn(io);
.observeOn(mainThread);
}
}
As you can see we can now tell the class the actual schedulers. In your tests you'd do something like:
public class MyAPIManagerTest {
private MyAPIManager myAPIManager;
#Test
public void getToken() throws Exception {
myAPIManager = new MyAPIManager(
Schedulers.trampoline(),
Schedulers.trampoline());
Observable<Token> o = myAPIManager.getToken("hello", "mytoken");
o.test().assertSubscribed();
o.test().assertValueCount(1);
}
}
The key points are:
You want it on the Schedulers.trampoline() scheduler to make sure everything's run on the JUnit thread
You need to be able to modify the schedulers while testing.
That's all. Hope it helps.
=========================================================
Here is Java version which I have used after following above Kotlin example:
public class TrampolineSchedulerRule implements TestRule {
#Override
public Statement apply(Statement base, Description description) {
return new MyStatement(base);
}
public class MyStatement extends Statement {
private final Statement base;
#Override
public void evaluate() throws Throwable {
try {
RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setNewThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setSingleSchedulerHandler(scheduler -> Schedulers.trampoline());
RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
base.evaluate();
} finally {
RxJavaPlugins.reset();
RxAndroidPlugins.reset();
}
}
public MyStatement(Statement base) {
this.base = base;
}
}
}
I have a problem with combining Google's Espresso test automation library.
In production code the following transformer to apply Schedulers to Observable is used:
public SCProductionUiSchedulersTransformer() {
schedulersTransformer = new Observable.Transformer() {
#Override
public Object call(Object observable) {
return ((Observable) observable)
.subscribeOn(rxFactory.getIoScheduler())
.observeOn(rxFactory.getMainThreadScheduler());
}
};
}
In Espresso automation tests the following is used:
public <T> SCIdlingUiSchedulersTransformer(Context context, final CountingIdlingResource countingIdlingResource) {
schedulersTransformer = new Observable.Transformer<T, T>() {
#Override
public Observable<T> call(Observable<T> observable) {
return observable
.subscribeOn(rxFactory.getIoScheduler())
.observeOn(rxFactory.getMainThreadScheduler())
.doOnSubscribe(new Action0() {
#Override
public void call() {
countingIdlingResource.increment();
}
})
.doOnTerminate(new Action0() {
#Override
public void call() {
countingIdlingResource.decrement();
}
});
}
};
}
CountingIdlingResource is the regular Espresso one used to inform automation Thread that application is not idle and it has to wait for the operation to complete.
For regular Observable the following code works without problems.
The problem is with Observable of type Subject. Of course it does not work, because subscription to Subject is made on Activity start and causes the automation Thread to hang.
Is there any way to align the code to make it work for Subject? Maybe detecting that observable is of type Subject?
I want to test UseCase object, in this specific case there is a LoginUseCase, which looks like this:
public class LoginUseCase implements RxUseCase<AuthResponse, AuthCredentials> {
ApiManager mApiManager;
public LoginUseCase(ApiManager apiManager) {
mApiManager =apiManager;
}
#Override
public Observable<AuthResponse> execute(final AuthCredentials authCredentials) {
return Observable.just(1)
.delay(750, TimeUnit.MILLISECONDS)
.flatMap(l -> mApiManager.login(authCredentials.getLogin(), authCredentials.getPassword()));
}
}
I wrote simple test:
#RunWith(MockitoJUnitRunner.class)
public class LoginUseCaseTest {
private LoginUseCase mLoginUseCase;
#Mock ApiManager mApiManager;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mLoginUseCase = new LoginUseCase(mApiManager);
}
#Test
public void testShouldThrowsError() throws Exception {
TestSubscriber<AuthResponse> testSubscriber = new TestSubscriber<>();
doReturn(Observable.error(new Throwable())).when(mApiManager).login("", "");
mLoginUseCase
.execute(new AuthCredentials("", ""))
.subscribe(testSubscriber);
testSubscriber.assertNoErrors();
}
}
But this test always passes and I don't know how mock error observable in this case.
EDIT: I've chaged testShouldThrowsError() according to SkinnyJ, but test still passes, any sugestions?
You need to call awaitTerminalEvent() on your test subscriber before assertions.
Because now, you schedule a delay to be run on Schedulers.computation and your test method successfully completes before completion of observable.
Alternative approach would be to pass scheduler as argument to execute method, or store scheduler in your usecase. This way, during test you can pass Schedulers.immediate() and your test will run on current thread (which will block execution for specified delay).
And last approach is to call toBlocking() on observable, but I think that passing scheduler is preferred choice. And there is no way to add this operator to your current observable.
I'm trying to test a presenter that use RxJava to retrieve data from an interactor. In the setup method I'm doing something like:
#Before
public void setup() {
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
}
So in my test method I can test the presenter call:
#Test
public void testLoad() {
presenter.load();
verify(view).dataLoaded(data);
verify(interactor).load();
}
If I run the test with Android Studio everything work as expected, the issue is that If I try on command line
gradle test
Then the test fails because:
Actually, there were zero interactions with this mock.
So I've tried to put a Thread.sleep(2000) right after the call to the presenter and then it works, so I guess the Schedulers.immediate(); is not working from command line but I have no idea why and how to debug/fix. Do you have any idea?
EDIT: presenter implementation ->
public void load() {
Observable<List<Data>> obs = interactor.load()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io());
obs.subscribe(new Observer<List<Data>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Data> data) {
view.dataLoaded(data);
}
});
}
You can mock RxJava Schedulers as well.
RxJavaHooks.reset();
RxJavaHooks.setOnIOScheduler(scheduler -> Schedulers.immediate());
Typically a good thing to call reset on setup & teardown.