I am using MVVM pattern with databinding. I have written tests. But I want them reviewed. The test is related to JUnit
test on the ViewModel.
FeedViewModelTest.java
@RunWith(PowerMockRunner.class)
@PrepareForTest({Observable.class, AndroidSchedulers.class})
@PowerMockIgnore("javax.net.ssl.*")
public class FeedViewModelTest {
FeedViewModel feedViewModel;
DataManager dataManager;
FeedViewModel.DataListener dataListener;
FeedApi feedApi;
@Before
public void setUp() {
dataListener = mock(FeedViewModel.DataListener.class);
Context mMockContext = mock(Context.class);
dataManager =mock(DataManager.class);
feedApi = mock(FeedApi.class);
feedViewModel = spy(new FeedViewModel(mMockContext, dataListener));
}
@Test
public void testShouldScheduleLoadFromAPIOnBackgroundThread() {
Observable<FeedResponse> observable = (Observable<FeedResponse>) mock(Observable.class);
when(dataManager.fetchFeed()).thenReturn(observable);
when(observable.subscribeOn(Schedulers.io())).thenReturn(observable);
when(observable.observeOn(AndroidSchedulers.mainThread())).thenReturn(observable);
//call test method
feedViewModel.fetchFeed();
verify(feedViewModel).fetchFeed();
TestSubscriber<FeedResponse> testSubscriber = new TestSubscriber<>();
observable.subscribeOn(Schedulers.io());
observable.observeOn(AndroidSchedulers.mainThread());
observable.subscribeWith(new DisposableObserver<FeedResponse>() {
@Override
public void onNext(FeedResponse value) {
dataListener.onDataChanged(value.getData());
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
dataListener.onError();
}
@Override
public void onComplete() {
}
});
//verify if all methods in the chain are called with correct arguments
verify(observable).subscribeOn(Schedulers.io());
verify(observable).observeOn(AndroidSchedulers.mainThread());
verify(observable).subscribeWith(Matchers.<DisposableObserver<FeedResponse>>any());
}
}
FeedViewModel.java
public class FeedViewModel {
public DataManager dataManager;
private DataListener datalistener;
private Context mContext;
public FeedViewModel(Context context, DataListener datalistener) {
this.datalistener = datalistener;
mContext = context;
dataManager = new DataManager();
}
public void fetchFeed() {
dataManager.fetchFeed()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<FeedResponse>() {
@Override
public void onError(Throwable e) {
e.printStackTrace();
datalistener.onError();
}
@Override
public void onComplete() {
}
@Override
public void onNext(FeedResponse feedResponse) {
if(datalistener!=null) {
datalistener.onDataChanged(feedResponse.getData());
}
}
});
}
public interface DataListener
{
void onDataChanged(List<FeedModel> model);
void onError();
}
}
DataManager.java
public class DataManager {
private FeedApi feedApi;
private Observable<FeedResponse> feedResponseObservable;
public FeedApi getFeedApi() {
return feedApi;
}
public DataManager()
{
feedApi = new FeedApi();
}
public Observable<FeedResponse> fetchFeed() {
feedResponseObservable = feedApi.fetchFeed(1);
return feedResponseObservable;
}
}
Testing libs used:
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.9.5'
testCompile 'org.powermock:powermock-api-mockito:1.5.6'
testCompile 'org.powermock:powermock-module-junit4:1.6.2'
DataListener
is an interface and is used as a callback to the activity. I have some views in activity that are visible or gone based on the data fetched from the server. Is my unit test for FeedViewModel correct?
Note: The test for FeedViewModelTest passes. I am using the RxJava2.
1 Answer 1
It's been a while and I haven't got a review for the question. I am attempting to answer my own question. Do point out any mistakes. Happy to take it.
Create Mocks
Define return values for methods with mockitos when
Call the test method
Verify all the methods are called in the chain with correct arguments
I changed my FeedViewModel
constructor
public FeedViewModel(Context context, DataListener datalistener,DataManager dataManager) {
this.datalistener = datalistener;
mContext = context;
this.dataManager = dataManager;
}
With the above I can pass actual arguments while fetching data and mock them while testing. Mockitos when requires mock objects.
With that in place, I had to pass the mocks while setting up in the test
Mocks:
dataListener = mock(FeedViewModel.DataListener.class);
Context mMockContext = mock(Context.class);
dataManager =mock(DataManager.class);
Then:
feedViewModel = spy(new FeedViewModel(mMockContext, dataListener,dataManager));
Then my final unit test:
@Test
public void testShouldScheduleLoadFromAPIOnBackgroundThread() {
Observable<FeedResponse> observable = (Observable<FeedResponse>) mock(Observable.class);
when(dataManager.fetchFeed()).thenReturn(observable);
when(observable.subscribeOn(Schedulers.io())).thenReturn(observable);
when(observable.observeOn(AndroidSchedulers.mainThread())).thenReturn(observable);
//call test method
feedViewModel.fetchFeed();
verify(feedViewModel).fetchFeed();
verify(observable).subscribeOn(Schedulers.io());
verify(observable).observeOn(AndroidSchedulers.mainThread());
verify(observable).subscribeWith(Matchers.<DisposableObserver<FeedResponse>>any());
}
To be sure that my code is correct, I checked some repos on github. Although I used MVVM pattern the test case is similar when you use MVP.
To test you need to have a clean architecture. With clear separation you can test your viewmodel with junit test and the UI with espresso.
I referred to this github repository to make sure my test code is correct.