Reputation: 1812
I am facing with Unit testing for the first time and I would like to know what is the best approach for the following scenario. I am using Mockito for the tests. The following test is for logic(Presenter) layer and I am trying to verify certain behaviors of the view.
App classes
The method of the Presenter that need to be include in the test:
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 (Default DisposableObserver base class to be used whenever we want default error handling):
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);
}
}
My test:
@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());
}
When I try to run the test, I get NullPointer, because new WeatherObserver(getMvpView())
is called, and in the BaseViewSubscriber
errorHandlerHelper is null because getApplicationCopomnent is null.
As well NullPointer is thrown in the static method AppUtils.isNetworkAvailable()
for the same reason.
When I try to comment these lines, the test is OK.
My questions are:
AppUtils.isNetworkAvailable()
? If yes, is it ok just because of
this method to use PowerMockito Runner@RunWith(PowerMockRunner.class)
?Upvotes: 0
Views: 576
Reputation: 10267
Should I use Dagger for the Unit test as well or? If yes please give me example for my test.
You don't have to use Dagger necessarily at the test, but that's where Dependency Injection will benefit you, as it will help you strip your dependencies out, and tests will be able to replace them.
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)?
Static methods are generally bad for testing, as you cannot replace them (at least not easily and without PowerMock) for testing purposes.
The better practice is to use Dagger
for the production code to inject those dependencies, preferably at Constructor, so at tests you can simply provide those dependencies according to test needs (using mocks or fakes where necessary).
In your case, you can add both ErrorHandlerHelper
and AppUtils
to BaseViewSubscriber
Constructor. as BaseViewSubscriber
shouldn't be injected, you will need to provide those modules to it from outside, in the presenter, that where you should use Injection to get those Objects. again at the Constructor.
At test, simply replace or provide this objects to the presenter that in it's turn will hand it over to the BaseViewSubscriber
.
You can read more about tests seams at android here.
Besides that, it some very odd to me the OO hierarchy of Observer
and Disposable
that wraps the Observable for getting common behavior, it's essentially breaking the functional stream oriented reactive approach, you might want to consider using patterns like compose using Transformers
and using doOnXXX operators do apply common behavior at reactive streams.
Upvotes: 2