ant2009
ant2009

Reputation: 22486

JVM unit testing with Mockito for testing Retrofit2 and RxJava for network requests

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

Answers (2)

ant2009
ant2009

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

Pravin Sonawane
Pravin Sonawane

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

Related Questions