I have developed an android app that is using retrofit with rxJava, and now I'm trying to set up the unit tests with Mockito but I don't know how to mock the api responses in order to create tests that do not do the real calls but have fake responses.
For instance, I want to test that the method syncGenres is working fine for my SplashPresenter. My classes are as follow:
public class SplashPresenterImpl implements SplashPresenter {
private SplashView splashView;
public SplashPresenterImpl(SplashView splashView) {
this.splashView = splashView;
}
@Override
public void syncGenres() {
Api.syncGenres(new Subscriber<List<Genre>>() {
@Override
public void onError(Throwable e) {
if(splashView != null) {
splashView.onError();
}
}
@Override
public void onNext(List<Genre> genres) {
SharedPreferencesUtils.setGenres(genres);
if(splashView != null) {
splashView.navigateToHome();
}
}
});
}
}
the Api class is like:
public class Api {
...
public static Subscription syncGenres(Subscriber<List<Genre>> apiSubscriber) {
final Observable<List<Genre>> call = ApiClient.getService().syncGenres();
return call
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(apiSubscriber);
}
}
Now I'm trying to test the SplashPresenterImpl class but I don't know how to do that, I should do something like:
public class SplashPresenterImplTest {
@Mock
Api api;
@Mock
private SplashView splashView;
@Captor
private ArgumentCaptor<Callback<List<Genre>>> cb;
private SplashPresenterImpl splashPresenter;
@Before
public void setupSplashPresenterTest() {
// Mockito has a very convenient way to inject mocks by using the @Mock annotation. To
// inject the mocks in the test the initMocks method needs to be called.
MockitoAnnotations.initMocks(this);
// Get a reference to the class under test
splashPresenter = new SplashPresenterImpl(splashView);
}
@Test
public void syncGenres_success() {
Mockito.when(api.syncGenres(Mockito.any(ApiSubscriber.class))).thenReturn(); // I don't know how to do that
splashPresenter.syncGenres();
Mockito.verify(api).syncGenres(Mockito.any(ApiSubscriber.class)); // I don't know how to do that
}
}
Do you have any idea about how should I mock and verify the api responses? Thanks in advance!
EDIT: Following @invariant suggestion, now I'm passing a client object to my presenter, and that api returns an Observable instead of a Subscription. However, I'm getting a NullPointerException on my Subscriber when doing the api call. The test class looks like:
public class SplashPresenterImplTest {
@Mock
Api api;
@Mock
private SplashView splashView;
private SplashPresenterImpl splashPresenter;
@Before
public void setupSplashPresenterTest() {
// Mockito has a very convenient way to inject mocks by using the @Mock annotation. To
// inject the mocks in the test the initMocks method needs to be called.
MockitoAnnotations.initMocks(this);
// Get a reference to the class under test
splashPresenter = new SplashPresenterImpl(splashView, api);
}
@Test
public void syncGenres_success() {
Mockito.when(api.syncGenres()).thenReturn(Observable.just(Collections.<Genre>emptyList()));
splashPresenter.syncGenres();
Mockito.verify(splashView).navigateToHome();
}
}
Why am I getting that NullPointerException?
Thanks a lot!
The first problem in your code is that you use static methods. This is not a testable architecture, at least not easily, because it makes it harder to mock the implementation.
To do things properly, instead of using Api
that accesses ApiClient.getService()
, inject this service to the presenter through the constructor:
public class SplashPresenterImpl implements SplashPresenter {
private SplashView splashView;
private final ApiService service;
public SplashPresenterImpl(SplashView splashView, ApiService service) {
this.splashView = splashView;
this.apiService = service;
}
Implement your JUnit test class and initialize the presenter with mock dependencies in the @Before
method:
public class SplashPresenterImplTest {
@Mock
ApiService apiService;
@Mock
SplashView splashView;
private SplashPresenter splashPresenter;
@Before
public void setUp() throws Exception {
this.splashPresenter = new SplashPresenter(splashView, apiService);
}
Then comes the actual mocking and testing, for example:
@Test
public void testEmptyListResponse() throws Exception {
// given
when(apiService.syncGenres()).thenReturn(Observable.just(Collections.emptyList());
// when
splashPresenter.syncGenres();
// then
verify(... // for example:, verify call to splashView.navigateToHome()
}
That way you can test your Observable + Subscription, if you want to test if the Observable behaves correctly, subscribe to it with an instance of TestSubscriber
.
When testing with RxJava and RxAndroid schedulers, such as Schedulers.io()
and AndroidSchedulers.mainThread()
you might encounter several problems with running your observable/subscription tests.
The first is NullPointerException
thrown on the line that applies given scheduler, for example:
.observeOn(AndroidSchedulers.mainThread()) // throws NPE
The cause is that AndroidSchedulers.mainThread()
is internally a LooperScheduler
that uses android's Looper
thread. This dependency is not available on JUnit test environment, and thus the call results in a NullPointerException.
The second problem is that if applied scheduler uses a separate worker thread to execute observable, the race condition occurs between the thread that executes the @Test
method and the said worker thread. Usually it results in test method returning before observable execution finishes.
Both of the said problems can be easily solved by supplying test-compliant schedulers, and there are few options:
Use RxJavaHooks
and RxAndroidPlugins
API to override any call to Schedulers.?
and AndroidSchedulers.?
, forcing the Observable to use, for example, Scheduler.immediate()
:
@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();
}
This code has to wrap the Observable test, so it can be done within @Before
and @After
as shown, it can be put into JUnit @Rule
or placed anywhere in the code. Just don't forget to reset the hooks.
Second option is to provide explicit Scheduler
instances to classes (Presenters, DAOs) through dependency injection, and again just use Schedulers.immediate()
(or other suitable for testing).
As pointed out by @aleien, you can also use an injected RxTransformer
instance that executes Scheduler
application.
I've used the first method with good results in production.