Mockito单元测试BaseObserver和静态方法与上下文

时间:2017-03-28 12:55:06

标签: android unit-testing mockito powermockito rx-java2

我第一次面对单元测试,我想知道以下场景的最佳方法是什么。 我正在使用Mockito进行测试。以下测试用于逻辑(Presenter)层,我正在尝试验证视图的某些行为。

应用类

需要包含在测试中的Presenter方法

public void loadWeather() {

CityDetailsModel selectedCity = getDbHelper().getSelectedCityModel();

if (selectedCity != null) {
  getCompositeDisposableHelper().execute(
      getApiHelper().weatherApiRequest(selectedCity.getLatitude(), selectedCity.getLongitude()),
      new WeatherObserver(getMvpView()));
} else {
  getMvpView().showEmptyView();
}

}

WeatherObserver

 public class WeatherObserver extends BaseViewSubscriber<DayMvpView, WeatherResponseModel> {

   public WeatherObserver(DayMvpView view) {
      super(view);
    }

    @Override public void onNext(WeatherResponseModel weatherResponseModel) {
      super.onNext(weatherResponseModel);

      if (weatherResponseModel.getData().isEmpty()) {
        getMvpView().showEmptyView();
      } else {
        getMvpView().showWeather(weatherResponseModel.getData());
      }
    }
  }

BaseViewSubscriber(每当我们想要默认错误处理时使用的默认DisposableObserver基类)

 public class BaseViewSubscriber<V extends BaseMvpView, T> extends DisposableObserver<T> {

  private ErrorHandlerHelper errorHandlerHelper;
  private V view;

  public BaseViewSubscriber(V view) {
    this.view = view;
    errorHandlerHelper = WeatherApplication.getApplicationComponent().errorHelper();
  }

  public V getView() {
    return view;
  }

  public boolean shouldShowError() {
    return true;
  }

  protected boolean shouldShowLoading() {
    return true;
  }

  @Override public void onStart() {
    if (!AppUtils.isNetworkAvailable(WeatherApplication.getApplicationComponent().context())) {
      onError(new InternetConnectionException());
      return;
    }

    if (shouldShowLoading()) {
      view.showLoading();
    }
    super.onStart();
  }

  @Override public void onError(Throwable e) {
    if (view == null) {
      return;
    }

    if (shouldShowLoading()) {
      view.hideLoading();
    }

    if (shouldShowError()) {
      view.onError(errorHandlerHelper.getProperErrorMessage(e));
    }
  }

  @Override public void onComplete() {
    if (view == null) {
      return;
    }

    if (shouldShowLoading()) {
      view.hideLoading();
    }
  }

  @Override public void onNext(T t) {
    if (view == null) {
      return;
    }
  }
}

CompositeDisposableHelper(CompositeDisposable helper class)

public class CompositeDisposableHelper {

  public CompositeDisposable disposables;
  public TestScheduler testScheduler;

  @Inject public CompositeDisposableHelper(CompositeDisposable disposables) {
    this.disposables = disposables;
    testScheduler = new TestScheduler();
  }

  public <T> void execute(Observable<T> observable, DisposableObserver<T> observer) {
    addDisposable(observable.subscribeOn(testScheduler)
        .observeOn(testScheduler)
        .subscribeWith(observer));
  }

  public void dispose() {
    if (!disposables.isDisposed()) {
      disposables.dispose();
    }
  }

  public TestScheduler getTestScheduler() {
    return testScheduler;
  }

  public void addDisposable(Disposable disposable) {
    disposables.add(disposable);
  }
}

我的测试

  @Test public void loadSuccessfully() {
    WeatherResponseModel responseModel = new WeatherResponseModel();
    List<WeatherModel> list = new ArrayList<>();
    list.add(new WeatherModel());
    responseModel.setData(list);

    CityDetailsModel cityDetailsModel = new CityDetailsModel();
    cityDetailsModel.setLongitude("");
    cityDetailsModel.setLatitude("");

    when(dbHelper.getSelectedCityModel()).thenReturn(cityDetailsModel);

    when(apiHelper.weatherApiRequest(anyString(), anyString())).thenReturn(
        Observable.just(responseModel));

    dayPresenter.loadWeather();

    compositeDisposableHelper.getTestScheduler().triggerActions();

    verify(dayMvpView).showWeather(list);
    verify(dayMvpView, never()).showEmptyView();
    verify(dayMvpView, never()).onError(anyString());
  }

当我尝试运行测试时,我得到NullPointer,因为new WeatherObserver(getMvpView())被调用,而BaseViewSubscriber errorHandlerHelper为null,因为getApplicationCopomnent为null。 由于同样的原因,静态方法AppUtils.isNetworkAvailable()中也会抛出NullPointer。

当我尝试评论这些行时,测试就可以了。

我的问题是:

  • 我也应该使用Dagger进行单元测试吗?如果是,请给 我的测试示例。
  • 我应该将PowerMockito用于静态方法 AppUtils.isNetworkAvailable()?如果是的话,是不是因为 这种方法使用PowerMockito Runner
    @RunWith(PowerMockRunner.class)

1 个答案:

答案 0 :(得分:2)

  

我是否应该使用Dagger进行单元测试?如果是,请举例说明我的测试。

您不必在测试中使用Dagger,但是依赖注入将使您受益的地方,因为它将帮助您剥离依赖项,并且测试将能够替换它们。

  

我应该将PowerMockito用于静态方法AppUtils.isNetworkAvailable()吗?如果是,只是因为这种方法使用PowerMockito Runner才行   @RunWith(PowerMockRunner.class)?

静态方法通常不适合测试,因为您无法替换它们(至少不容易和没有PowerMock)以进行测试。
更好的做法是使用Dagger来生成代码以注入这些依赖项,最好是在Constructor中,因此在测试中,您可以根据测试需求简单地提供这些依赖项(在必要时使用模拟或伪造)。

在您的情况下,您可以将ErrorHandlerHelperAppUtils添加到BaseViewSubscriber构造函数中。由于BaseViewSubscriber不应该注入,您需要在演示者中从外部提供这些模块,您应该使用注入来获取这些对象。再次在建设者。
在测试时,只需将此对象替换或提供给演示者,该演示者将把它交给BaseViewSubscriber

您可以在android here上阅读有关测试接缝的更多信息。

除此之外,对我来说,ObserverDisposable的OO层次结构非常奇怪,它包含Observable以获得共同的行为,它本质上打破了功能流导向的被动方法,您可能需要考虑使用Transformerscompose这样的模式,并使用doOnXXX运算符在反应流中应用常见行为。