I am trying to test my ViewModel in my application, here is the constructor:
#Inject
public SearchUserViewModel(#Named("searchUser") UseCase searchUserUseCase) {
this.searchUserUseCase = searchUserUseCase;
}
In my test I create a SearchUserUseCase with mocks like this:
Observable error = Observable.error(new Throwable("Error"));
when(gitHubService.searchUser(MockFactory.TEST_USERNAME_ERROR)).thenReturn(error);
when(ObserverThread.getScheduler()).thenReturn(Schedulers.immediate());
when(SubscriberThread.getScheduler()).thenReturn(Schedulers.immediate());
searchUserUseCase = new SearchUserUseCase(gitHubService, SubscriberThread, ObserverThread);
In my ViewModel class I have this snippet which I want to test:
public void onClickSearch(View view) {
loadUsers();
}
private void loadUsers() {
if (username == null) {
fragmentListener.showMessage("Enter a username");
} else {
showProgressIndicator(true);
searchUserUseCase.execute(new SearchUserSubscriber(), username);
}
}
private final class SearchUserSubscriber extends DefaultSubscriber<SearchResponse> {
#Override
public void onCompleted() {
showProgressIndicator(false);
}
#Override
public void onError(Throwable e) {
showProgressIndicator(false);
fragmentListener.showMessage("Error loading users");
}
#Override
public void onNext(SearchResponse searchResponse) {
List<User> users = searchResponse.getUsers();
if (users.isEmpty()) {
fragmentListener.showMessage("No users found");
} else {
fragmentListener.addUsers(users);
}
}
}
Finally in my test I have this:
#Test
public void shouldDisplayErrorMessageIfErrorWhenLoadingUsers() {
SearchUserViewModel searchUserViewModel = new SearchUserViewModel(searchUserUseCase);
searchUserViewModel.setFragmentListener(mockFragmentListener);
searchUserViewModel.setUsername(MockFactory.TEST_USERNAME_ERROR);
searchUserViewModel.onClickSearch(view);
verify(mockFragmentListener).showMessage("Error loading users");
}
I get this error from Mockito:
Wanted but not invoked:
fragmentListener.showMessage(
"Error loading users"
);
I am not sure if this is a good test, but I somehow want to test the SearchUserSubscriber one way or another. Thanks
Edit: I have found similar questions to this problem here: Can't verify mock method call from RxJava Subscriber (which still isn't answered) and here: Verify interactions in rxjava subscribers. The latter question is similar but does not execute the subscriber in a separate class (which happens in SearchUserUseCase here).
I also tried RobolectricGradleTestRunner instead of MockitoJunitRunner and changed to Schedulers.io() and AndroidSchedulers.mainThread(), but I still get the same error.
Tried mocking SearchUserUseCase instead of GitHubService (which feels cleaner), but I'm not sure on how to test the subscriber that way since that is passed as an argument to the void method execute() in UseCase.
public void execute(Subscriber useCaseSubscriber, String query) {
subscription = buildUseCase(query)
.observeOn(postExecutionThread.getScheduler())
.subscribeOn(threadExecutor.getScheduler())
.subscribe(useCaseSubscriber);
}
And buildUseCase()
#Override
public Observable buildUseCase(String username) throws NullPointerException {
if (username == null) {
throw new NullPointerException("Query must not be null");
}
return getGitHubService().searchUser(username);
}
For me it worked out to add a Observable.Transformer<T, T> as followed:
void gatherData() {
service.doSomeMagic()
.compose(getSchedulerTransformer())
.subscribe(view::displayValue);
}
private <T> Observable.Transformer<T, T> getSchedulerTransformer() {
if (mTransformer == null) {
mTransformer = (Observable.Transformer<T, T>) observable -> observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
return mTransformer;
}
void setSchedulerTransformer(Observable.Transformer<Observable<?>, Observable<?>> transformer) {
mTransformer = transformer;
}
And to set the Transformer. I just passed this:
setSchedulerTransformer(observable -> {
if (observable instanceof Observable) {
Observable observable1 = (Observable) observable;
return observable1.subscribeOn(Schedulers.immediate())
.observeOn(Schedulers.immediate());
}
return null;
});
So just add a #Before method in your test and call presenter.setSchedulerTransformer and it should be able to test this. If you want more detail check this answer.
If you are using Mockito, you can probably get hold of a SearchUserSubscriber using an ArgumentCaptor, for example...
#Captor
private ArgumentCaptor<SearchUserSubscriber> subscriberCaptor;
private SearchUserSubscriber getSearchUserSubscriber() {
// TODO: ...set up the view model...
...
// Execute the code under test (making sure the line 'searchUserUseCase.execute(new SearchUserSubscriber(), username);' gets hit...)
viewModel.onClickSearch(view);
verify(searchUserUseCase).execute(subscriberCaptor.capture(), any(String.class));
return subscriberCaptor.getValue();
}
Now you can have test cases such as...
#Test
public void shouldDoSomethingWithTheSubscriber() {
SearchUserSubscriber subscriber = getSearchUserSubscriber();
...
}
Related
I am using Mockito for writing unit test case in Android. I am stuck into one method where I am modify Object and pass it to mocked method, Not able to understand how to write unit test case for this
Class LocationViewModel{
private LocationInteractor locationInteractor;
LocationViewModel (LocationInteractor locationInteractor){
this.locationInteractor =locationInteractor;
}
#Override
public Single<List<String>> getRecentLocations( LocationViewType locationViewType) {
return locationInteractor.getUpdatedRecentLocation(getRecentLocationFilter(locationViewType),locationViewType);
}
private Map<String, String[]> getRecentLocationFilter(LocationViewType locationViewType) {
LocationFilter locationfilter = new LocationFilter();
if (locationViewType == LocationViewType.DEFAULT_LOCATIONS) {
return locationFilter.getRecentDefaultLocationFilter();
} else if (locationViewType == SETTING_LOCATIONS) {
return locationFilter.getRecentSettingLocationFilter();
} else if (locationViewType == LocationViewType.INVENTORY_LOCATION) {
return locationFilter.getRecentSettingLocationFilter();
} else {
return locationFilter.getRecentCurrentLocationFilter();
}
}
}
Class LocationViewModelTest{
#Mock private LocationInteractorContract mockLocationInteractor;
private LocationViewModelContract locationViewModel;
#Before
public void setUp() {
initMocks(this);
locationViewModel = new LocationViewModel(mockLocationInteractor)
}
#Test
public void getRecentLocationsList_check_for_Null() {
when(mockLocationInteractor.getUpdatedRecentLocation(anyMap(),LocationViewType.SETTING_LOCATIONS)) ......Line 1
.thenReturn(Single.error(NullPointerException::new));
locationViewModel
.getRecentLocations(LocationViewType.SETTING_LOCATIONS)
.test()
.assertFailure(NullPointerException.class);
}
}
When I use anyMap() in Line no 1 it throws - org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
When I use new HashMap<>() in Line no 1 it throws NullPointerException
Want to write test case for method - getRecentLocations where getRecentLocationFilter is private method
For the InvalidUseOfMatchersException, the reason is probably that you have to use either all values or all matchers. For example:
when(mockLocationInteractor.getUpdatedRecentLocation(anyMap(), any())
I tried to use paging in my project. Unfortunately, it works not as I expected. I expected that the liveDataObserver will work after callBack.onResult.But in fact, the liveDataObserver observes immediately when the loadInitial finished.The callBack works later, and didn't post data to the observer.
The code:
First I wrote a class extend PageKeyedDataSource and interface SingleCreator
public class MyPagingDataSource<T> extends PageKeyedDataSource<Integer, T>
public interface SingleCreator<T> {
SingleSubscribeProxy<Page<T>> createSingle(int page, int pageSize);
}
Then the constructor of MyPagingDataSource:
public MyPagingDataSource(SingleCreator<T> singleCreator) {
this.singleCreator = singleCreator;
}
And override loadInitial:
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull LoadInitialCallback<Integer, T> callback) {
singleCreator.createSingle(1, params.requestedLoadSize)
.subscribe(new SingleObserver<Page<T>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Page<T> ts) {
callback.onResult(ts.list, ts.pageNumber, ts.total, ts.pageNumber - 1, ts.pageNumber + 1);
Timber.d("registerLiveData" + ts.list.size());
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
});
try {
//when I add this, observer will work after callback
//And if not observer works before callback.onResult
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Then the datasourceFactory is just newInstanced in viewModel:
public LiveData<PagedList<MyData>> page = loadPageData();
public LiveData<PagedList<MyData>> loadPageData() {
return new LivePagedListBuilder<>(new DataSource.Factory<Integer, MyData>() {
#Override
public DataSource<Integer, MyData> create() {
return new HBPagingDataSource<>((page, pageSize) -> loadPageSingle(page, pageSize));
}
}, 2).build();
}
the single
private SingleSubscribeProxy<Page<MyData>> loadPageSingle(int pageNum, int pageSize) {
return mModel.loadMyDates(pageNum, pageSize)
.doOnError(Throwable::printStackTrace)
.as(autoDisposable(this));
}
at fragment
mViewModel.page.observe(this, myDatas -> {
Timber.d("registerLiveData%s", myDatas.size());
myAdapter.submitList(myDatas);
});
Maybe related things:
I wrote subscribeOn and observeOn in retrofit's callAdapter
The viewModel is a scopeProvider since I'm using autoDispose
I tried some example in github. And it seems, the setValue for pageLivedata is always work after loadInitial. In this case, how can I use single?
It's seems solved.
The error is because schedule the thread using rxjava.
It makes single and datasource work in different thread.
In this case, callback onResult run after the observer.
So, I updated the callAdapter where I wrote subscribeOn and observeOn for single.
Filter by className when It's Page class, it won't do subscribeOn and observeOn.
Now the conclusion is, let paging handle the thread.
Is there any way to enforce non-nullability of LiveData values? Default Observer implementation seems to have #Nullable annotation which forces an IDE to suggest that the value might be null and should be checked manually:
public interface Observer<T> {
/**
* Called when the data is changed.
* #param t The new data
*/
void onChanged(#Nullable T t);
}
A new option is available if you use Kotlin. You can replace LiveData with StateFlow. It is more suitable for Kotlin code and provides built-in null safety.
Instead of using:
class MyViewModel {
val data: LiveData<String> = MutableLiveData(null) // the compiler will allow null here!
}
class MyFragment: Fragment() {
model.data.observe(viewLifecycleOwner) {
// ...
}
}
You can use:
class MyViewModel {
val data: StateFlow<String> = MutableStateFlow(null) // compilation error!
}
class MyFragment: Fragment() {
lifecycleScope.launch {
model.data.collect {
// ...
}
}
}
StateFlow is part of coroutines and to use the lifecycleScope you need to add the lifecycle-extensions dependency:
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
Note that this API has been experimental before coroutines 1.4.0.
Here's some additional reading about replacing LiveData with StateFlow.
As Igor Bubelov pointed out, another advantage of this approach is that it's not Android specific so it can be used in shared code in multiplatform projects.
If you use Kotlin, you can create much nicer non-null observe function with extension. There is an article about it. https://medium.com/#henrytao/nonnull-livedata-with-kotlin-extension-26963ffd0333
It's possible to do it safely only if you are in control of the code which sets the data because you'll also have to wrap the LiveData class. This way the data setting methods will be protected with #NonNull and you can be sure that the data has already been checked before reaching the Observer.
Wrap the LiveData class:
public class NonNullMutableLiveData<T> extends MutableLiveData<T> implements NonNullLiveData<T> {
private final #NonNull T initialValue;
public NonNullMutableLiveData(#NonNull T initialValue) {
this.initialValue = initialValue;
}
#Override
public void postValue(#NonNull T value) {
super.postValue(value);
}
#Override
public void setValue(#NonNull T value) {
super.setValue(value);
}
#NonNull
#Override
public T getValue() {
//the only way value can be null is if the value hasn't been set yet.
//for the other cases the set and post methods perform nullability checks.
T value = super.getValue();
return value != null ? value : initialValue;
}
//convenience method
//call this method if T is a collection and you modify it's content
public void notifyContentChanged() {
postValue(getValue());
}
public void observe(#NonNull LifecycleOwner owner, #NonNull NonNullObserver<T> observer) {
super.observe(owner, observer.getObserver());
}
}
Create an interface for exposing as immutable:
public interface NonNullLiveData<T> {
#NonNull T getValue();
void observe(#NonNull LifecycleOwner owner, #NonNull NonNullObserver<T> observer);
}
Finally, wrap the Observer:
//not implementing Observer<T> to make sure this class isn't passed to
//any class other than NonNullMutableLiveData.
public abstract class NonNullObserver<T> {
public Observer<T> getObserver() {
return new ActualObserver();
}
public abstract void onValueChanged(#NonNull T t);
private class ActualObserver implements Observer<T> {
#Override
public void onChanged(#Nullable T t) {
//only called through NonNullMutableLiveData so nullability check has already been performed.
//noinspection ConstantConditions
onValueChanged(t);
}
}
}
Now you can create your data like this:
class DataSource {
private NonNullMutableLiveData<Integer> data = new NonNullMutableLiveData<>(0);
public NonNullLiveData<Integer> getData() {
return data;
}
}
And use it like this:
dataSource.getData().observe(this, new NonNullObserver<Integer>() {
#Override
public void onValueChanged(#NonNull Integer integer) {
}
});
Completely null safe.
While there a few things you can do, it is your responsibility to make sure you don't pass null to the LiveData. In addition to that, every 'solution' is more a suppression of the warning, which can be dangerous (if you do get a null value, you might not handle it and Android Studio will not warn you).
Assert
You can add assert t != null;. The assert will not be executed on Android, but Android Studio understands it.
class PrintObserver implements Observer<Integer> {
#Override
public void onChanged(#Nullable Integer integer) {
assert integer != null;
Log.d("Example", integer.toString());
}
}
Suppress the warning
Add an annotation to suppress the warning.
class PrintObserver implements Observer<Integer> {
#Override
#SuppressWarnings("ConstantConditions")
public void onChanged(#Nullable Integer integer) {
Log.d("Example", integer.toString());
}
}
Remove the annotation
This also works in my installation of Android Studio, but it might not work for you, but you could try to just remove the #Nullable annotation from the implementation:
class PrintObserver implements Observer<Integer> {
#Override
public void onChanged(Integer integer) {
Log.d("Example", integer.toString());
}
}
Default methods
It's unlikely you can use this on Android, but purely from a Java perspective, you could define a new interface and add a null check in a default method:
interface NonNullObserver<V> extends Observer<V> {
#Override
default void onChanged(#Nullable V v) {
Objects.requireNonNull(v);
onNonNullChanged(v);
// Alternatively, you could add an if check here.
}
void onNonNullChanged(#NonNull V value);
}
fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, observer: (t: T) -> Unit) {
this.observe(owner, Observer {
it?.let(observer)
})
}
You would have to do some additional work to handle null values that come from the library itself.
For example, when you return a LiveData from a #Dao in Room, like:
#Dao interface UserDao {
#get:Query("SELECT * FROM users LIMIT 1")
val user: LiveData<User>
}
And observe the user live data, it will call the onChanged callback with a null value if there is no user.
so.. imagine I have a method construct like this:
LocalDatabase:
public Observable<PoiObject> getPoiObject() {
return Observable.defer {
PoiObject object = poiDao.getPoiObject();
if(object == null) {
return Observable.empty();
}
else {
return Observable.just(object);
}
}
}
now, I have another method somewhere else that goes like this:
Service:
public Observable<PoiObject> getPoiObject() {
return localDatabase.getPoiObject()
}
public Observable<PoiObject> getItFromWeb() {
return restService.getObject()
}
if I try to chain up the call of the Service::getPoiObject into a Rx call like this:
Usecase:
public Observable<SomeVM> getObject() {
return service.getPoiObject()
.switchIfEmpty(service.getItFromWeb())
}
Then the following unit test fails:
#Test
public void test_getObject() {
Service service = mock()
when(service.getPoiObject()).thenReturn(any());
Observable<SomeVM> observable = usecase.getObject();
verify(service).getPoiObject();
verify(service, times(0)).getItFromWeb();
}
Why would getItFromWeb() execute when clearly,the previous call is not empty (object is returned from service.getPoiObject() call). Is there any other strategy to test upon switchIfEmpty?
Opening a brace doesn't magically make the code/variable beyond it get initialized in a lazy manner. What you wrote is this:
public Observable<SomeVM> getObject() {
Observable o1 = service.getPoiObject();
Observable o2 = service.getItFromWeb(); // <-------------------
Observable o3 = o1.switchIfEmpty(o2);
return o3;
}
You already did the reasonable job in getPoiObject() by deferring execution, which you should apply in getObject() as well:
public Observable getObject() {
return service.getPoiObject()
.switchIfEmpty(Observable.defer(() -> getItFromWeb()));
}
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:
Should I use Dagger for the Unit test as well or? If yes please give
me example for my test.
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)?
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.