Mockito 单元测试 BaseObserver 和带有上下文的静态方法

Mockito unit test BaseObserver and static method with context

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

应用classes

需要包含在测试中的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 基础 class 在我们需要默认错误处理时使用):

 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 助手 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。

当我尝试注释这些行时,测试正常。

我的问题是:

Should I use Dagger for the Unit test as well or? If yes please give me example for my test.

你不一定非得在测试中使用 Dagger,但这正是依赖注入对你有益的地方,因为它会帮助你去除依赖,测试将能够替换它们。

Should I use PowerMockito for the static method AppUtils.isNetworkAvailable()? If yes, is it ok just because of this method to use PowerMockito Runner @RunWith(PowerMockRunner.class)?

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

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

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

除此之外,ObserverDisposable 的 OO 层次结构包装 Observable 以获得常见行为对我来说有些奇怪,它本质上打破了面向功能流的反应式方法,您可能想考虑使用像 compose 这样的模式,使用 Transformers 和使用 doOnXXX 运算符确实在反应流中应用了常见的行为。