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。
当我尝试注释这些行时,测试正常。
我的问题是:
- 我是否也应该使用 Dagger 进行单元测试?如果是请给
我的测试示例。
- 我应该将 PowerMockito 用于静态方法吗
AppUtils.isNetworkAvailable()
?如果是,是否可以仅仅因为
此方法使用 PowerMockito Runner
@RunWith(PowerMockRunner.class)
?
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 中,这样在测试时您可以根据测试需要简单地提供这些依赖项(必要时使用模拟或伪造)。
在您的情况下,您可以将 ErrorHandlerHelper
和 AppUtils
添加到 BaseViewSubscriber
构造函数。由于不应注入 BaseViewSubscriber
,因此您需要从外部向它提供这些模块,在演示者中,您应该在其中使用注入来获取这些对象。再次在构造函数中。
在测试时,只需将此对象替换或提供给演示者,演示者又会将其移交给 BaseViewSubscriber
.
您可以在 android here 阅读更多关于测试接缝的信息。
除此之外,Observer
和 Disposable
的 OO 层次结构包装 Observable 以获得常见行为对我来说有些奇怪,它本质上打破了面向功能流的反应式方法,您可能想考虑使用像 compose 这样的模式,使用 Transformers
和使用 doOnXXX 运算符确实在反应流中应用了常见的行为。
我是第一次面对单元测试,我想知道以下场景的最佳方法是什么。 我正在使用 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。
当我尝试注释这些行时,测试正常。
我的问题是:
- 我是否也应该使用 Dagger 进行单元测试?如果是请给 我的测试示例。
- 我应该将 PowerMockito 用于静态方法吗
AppUtils.isNetworkAvailable()
?如果是,是否可以仅仅因为 此方法使用 PowerMockito Runner
@RunWith(PowerMockRunner.class)
?
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 中,这样在测试时您可以根据测试需要简单地提供这些依赖项(必要时使用模拟或伪造)。
在您的情况下,您可以将 ErrorHandlerHelper
和 AppUtils
添加到 BaseViewSubscriber
构造函数。由于不应注入 BaseViewSubscriber
,因此您需要从外部向它提供这些模块,在演示者中,您应该在其中使用注入来获取这些对象。再次在构造函数中。
在测试时,只需将此对象替换或提供给演示者,演示者又会将其移交给 BaseViewSubscriber
.
您可以在 android here 阅读更多关于测试接缝的信息。
除此之外,Observer
和 Disposable
的 OO 层次结构包装 Observable 以获得常见行为对我来说有些奇怪,它本质上打破了面向功能流的反应式方法,您可能想考虑使用像 compose 这样的模式,使用 Transformers
和使用 doOnXXX 运算符确实在反应流中应用了常见的行为。