Reputation: 22486
Android Studio 2.3 RC 1
I am using the MVP architecture and want to run JVM unit tests.
In my Model I am using Retrofit2 and RxJava to fetch movies from a API. I want to test the function getPopularMovies(...)
However, this function will make a call to the webserver. However, in the test I want to mock this somehow and just test the onSuccess()
and onFailure()
methods are called.
My model class looks like this snippet only to keep it short:
public class MovieListModelImp implements MovieListModelContract {
@Override
public void getPopularMovies(PopularMovieResultsListener popularMovieResultsListener) {
mSubscription = mMovieAPIService.getPopular(Constants.MOVIES_API_KEY)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Results>() {
@Override
public void onCompleted() {
Timber.d("onCompleted");
}
@Override
public void onError(Throwable e) {
Timber.e(e, "onError");
popularMovieResultsListener.onFailure(e.getMessage());
}
@Override
public void onNext(Results results) {
Timber.d("onNext %d", results.getResults().size());
popularMovieResultsListener.onSuccess(results);
}
});
}
}
And the interface:
public interface MovieListModelContract {
interface PopularMovieResultsListener {
void onFailure(String errorMessage);
void onSuccess(Results popularMovies);
}
void getPopularMovies(PopularMovieResultsListener popularMovieResultsListener);
}
My problem I am trying to solve is how can I use Mockito to test the getPopularMovies
without actually calling the network service? I just want to test that:
popularMoviesResultsListener.onFailure(e.getMessage())
will be called on failure to get movies
and
popularMovieResultsListener.onSuccess(results);
will be called on success when movies are recieved
I have a test like this but I am not sure if this is correct:
@Test
public void shouldDisplaySuccessWhenNetworkSucceeds() {
/* Results is the movie results class that is returned */
Results results = new Results();
/* Mock the listener */
MovieListModelContract.PopularMovieResultsListener mockPopularMoviesResultsListener =
Mockito.mock(MovieListModelContract.PopularMovieResultsListener.class);
/* Real instance of the model */
MovieListModelImp movieListModelImp = new MovieListModelImp();
/* Call getPopularMovies with mock listener - However, this will still make a real network request */
movieListModelImp.getPopularMovies(mockPopularMoviesResultsListener);
/* Verify - but I think I have got this all wrong */
verify(mockPopularMoviesResultsListener, times(1)).onSuccess(results);
}
So my problem is how can I mock a call to a network request and test the expected onSuccess() and onFailure() is working correctly?
Upvotes: 7
Views: 2920
Reputation: 22486
I have managed to complete my answer here. I am not sure if it is the best way and hopefully other people might be able to comment.
For the setup:
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(MovieListModelImpTest.this);
movieListModelContract = new MovieListModelImp(mockMovieAPIService);
RxJavaHooks.setOnIOScheduler(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();
}
});
}
And the tear down
@After
public void tearDown() throws Exception {
RxJavaHooks.reset();
RxAndroidPlugins.getInstance().reset();
}
My Service API
@GET("movie/popular")
Observable<Results> getPopular(@Query("api_key") String apikey);
Mocks
@Mock MovieAPIService mockMovieAPIService;
@Mock Observable<Results> mockCall;
@Mock ResponseBody responseBody;
@Mock MovieListModelContract.PopularMovieResultsListener mockPopularMoviesResultsListener;
private MovieListModelContract movieListModelContract;
@Captor ArgumentCaptor<Callback<List<Results>>> argumentCaptor;
My test
@Test
public void shouldDisplaySuccessMessageOnSuccess() {
final Results results = new Results();
when(mockMovieAPIService.getPopular(anyString())).thenReturn(Observable.just(results));
movieListModelContract.getPopularMovies(mockPopularMoviesResultsListener);
verify(mockPopularMoviesResultsListener, never()).onFailure(anyString());
verify(mockPopularMoviesResultsListener, times(1)).onSuccess(results);
}
I have given a example of a success case here which works ok as an example. However, as everything seems to work ok. I am just wondering is there a better way of doing it, or is there any mistakes in what I have done?
Many thanks in advance
Upvotes: 3
Reputation: 1833
The idea is to user TestSubscriber
to assert in unit tests.
Return the Observable from Retrofit instead of void
(Note that I have removed the listener PopularMovieResultsListener
as you are using RxJava. With RxJava you can subscribe to the returned Observable and use onNext()
, onComplete()
, onError()
instead.)
public class MovieListModelImp implements MovieListModelContract {
@Override
public Observable<Results> getPopularMovies() {
/** Return the Observable from Retrofit. */
return mMovieAPIService.getPopular(Constants.MOVIES_API_KEY);
}
Use
TestSubscriber
in your unit tests to assert
@Mock
MovieAPIService mMovieAPIService
@Test
public void shouldDisplaySuccessWhenNetworkSucceeds() {
/* Results is the movie results class that is returned */
Results expectedResults = new Results();
MovieListModelImp movieListModelImp = new MovieListModelImp();
//Mock mMovieAPIService which is the actual network call
when(mMovieAPIService.getPopular(any(String.class)).thenReturn(Observable.just(results));
Observable<Results> actualResultsObservable = movieListModelImp.getPopularMovies();
TestObserver<Results> testObserver = actualResultsObservable.test();
testObserver.assertSubscribed();
testObserver.assertResult(expectedResults);
//verify
verify(mMovieAPIService, times(1)).getPopular(any(String.class));
}
Upvotes: 4