Android MVP testing with upgradeability
I am developing an application based on MVP pattern using modification to work on the web. I want to unit test my presenter, but it doesn't work.
In my application, the DataView implements DataView
that mocks Mockito. In
DataPresenter
a onViewCreated
method, an MyApi
instance gets from MyApplication
and executes the request. Anonymous Subscriber<Data>
onNext calls showData(Data data)
on DataView
. Unfortunately the Mockito.verify(dataView).showData(data)
test doesn't pass. I mocked the modified client's self by responding in a deterministic way.
Code below:
public class DataFragment extends ProgressFragment implements DataView {
protected DataPresenter mDataPresenter;
//[...] initialization arguments boilerplate etc.
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mDataPresenter.onViewCreated(mId);
//[...]
}
@Override
public void startLoading() {
setContentShown(false);
}
@Override
public void stopLoading() {
setContentShown(true);
}
@Override
public void showData(Data data) {
setContentEmpty(false);
//[...] present data
}
@Override
public void showError() {
setContentEmpty(true);
setEmptyText(R.string.unknown_error);
}
}
In DataPresenter
:
@Override
public void onViewCreated(long id) {
getView().startLoading();
MyApplication.getInstance().getMyApi().checkIn(User.getUser().getFormattedTokenForRequest(),
(int) id).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(new Subscriber<Data>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
getView().showError();
getView().stopLoading();
}
@Override
public void onNext(Data data) {
getView().showData(data);
getView().stopLoading();
}
});
;
}
My test case:
public static final String GOOD_RESPONSE = "[Data in JSON]"
public static final int GOOD_STATUS = 201;
@Mock
DataView mDataView;
@Mock
MyApplication app;
@Mock
SharedPreferencesManager mSharedPreferencesManager;
DataPresenter mDataPresenter;
@Before
public void setUp() throws Exception {
mDataPresenter = new DataPresenterImpl(mDataView);
MyApplication.setInstance(app);
Mockito.when(app.getSharedPreferencesManager()).thenReturn(mSharedPreferencesManager);
Mockito.when(mSharedPreferencesManager.getUser()).thenReturn(null);
}
@Test
public void testCase() throws Exception {
RestAdapter adapter = (new RestAdapter.Builder()).setEndpoint(URL)
.setClient(new MockClient(GOOD_RESPONSE, GOOD_STATUS))
.build();
Mockito.when(app.getMyApi()).thenReturn(adapter.create(MyApi.class));
mCheckInPresenter.onViewCreated(3);
Mockito.verify(checkInView).startLoading();
Mockito.verify(checkInView).showData(new Data());
}
The test fails with the error "Required but not called: dataView.showData (...".
What's interesting Response execute()
is called in MockClient
, but onNext(Data data)
in the subscriber included in DataPresenterImpl
, not. Any ideas? I guess the problem is with the asynchronous request.
source to share
The problem is that the work is being sent to a different thread and the mockito cant check what is going on. My solution would be to create a factory planner and mock it and return the main thread for tests like this. Something like:
public class schedulerFactory {
public Scheduler io() {
return Schedulers.io();
}
//etc
}
then in your test you should write something like this:
@Mock SchedulerFactory factory
@Before
public void setUp() {
when(factory.io()).thenReturn(Schedulers.mainThread());
}
in general it's a good idea to run all the code in the same thread for testing
source to share