使用Mockito进行JVM单元测试,以测试Retrofit2和RxJava的网络请求

时间:2017-02-24 18:19:27

标签: android mockito rx-java retrofit2

Android Studio 2.3 RC 1

我正在使用MVP架构并希望运行JVM单元测试。

在我的模型中,我使用Retrofit2和RxJava从API获取电影。我想测试函数getPopularMovies(...)但是,这个函数会调用webserver。但是,在测试中我想以某种方式模拟它,只是测试onSuccess()onFailure()方法被调用。

我的模型类看起来像这个片段只是为了保持简短:

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);
            }
        });
    }
}

界面:

public interface MovieListModelContract {
    interface PopularMovieResultsListener {
        void onFailure(String errorMessage);
        void onSuccess(Results popularMovies);
    }
    void getPopularMovies(PopularMovieResultsListener popularMovieResultsListener);
}

我想解决的问题是如何在不实际调用网络服务的情况下使用Mockito来测试getPopularMovies?我只想测试一下: 如果未能获得电影,将会调用popularMoviesResultsListener.onFailure(e.getMessage()) 和 收到电影后,popularMovieResultsListener.onSuccess(results);将被称为成功

我有这样的测试,但我不确定这是否正确:

@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);
}

所以我的问题是如何模拟对网络请求的调用并测试预期的onSuccess()和onFailure()是否正常工作?

2 个答案:

答案 0 :(得分:4)

想法是用户TestSubscriber在单元测试中断言。

  

从Retrofit返回Observable而不是void

(请注意,我在使用RxJava时删除了侦听器PopularMovieResultsListener。使用RxJava,您可以订阅返回的Observable并使用onNext()onComplete()onError()来代替。)

public class MovieListModelImp implements MovieListModelContract {

    @Override
    public Observable<Results> getPopularMovies() {
        /** Return the Observable from Retrofit. */
        return mMovieAPIService.getPopular(Constants.MOVIES_API_KEY);
}
  

在单元测试中使用TestSubscriber来断言

@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));

}

答案 1 :(得分:3)

我已经设法完成了我的答案。我不确定这是否是最好的方式,希望其他人也可以评论。

对于设置:

<ion-view title="Page1" id="page18">
<ion-content padding="true" class="has-header">
<button id="page4-button4" class="button button-positive  button-block" ng-click="scanBarcode()">Scan</button>
<button id="page4-button5" class="button button-positive  button-block">Display Scanned Data</button>

然后拆掉

@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();
        }
    });
}

我的服务API

  @After
    public void tearDown() throws Exception {
        RxJavaHooks.reset();
        RxAndroidPlugins.getInstance().reset();
    }

嘲笑

@GET("movie/popular")
Observable<Results> getPopular(@Query("api_key") String apikey);

我的测试

@Mock MovieAPIService mockMovieAPIService;
@Mock Observable<Results> mockCall;
@Mock ResponseBody responseBody;
@Mock MovieListModelContract.PopularMovieResultsListener mockPopularMoviesResultsListener;
private MovieListModelContract movieListModelContract;
@Captor ArgumentCaptor<Callback<List<Results>>> argumentCaptor;

我举了一个成功案例的例子,作为一个例子。但是,因为一切似乎都可行。我只是想知道是否有更好的方法,或者我做了什么错误?

非常感谢提前