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()));
}
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())
This is my method on Retrofit:
#GET("comments")
Callable<List<Comments>> getCommentsRx();
I have created Thread class for Rxjava stuff :
public static <T> Disposable async(Callable<List<T>> task, Consumer<List<T>> finished, Consumer<Throwable> onError) {
return async(task, finished, onError, Schedulers.io());
}
public static <T> Disposable async(Callable<List<T>> task, Consumer<List<T>> finished,
Consumer<Throwable> onError, Scheduler scheduler) {
finished = finished != null ? finished
: (a) -> {
};
onError = onError != null ? onError
: throwable -> {
};
return Single.fromCallable(task)
.subscribeOn(scheduler)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(finished, onError);
}
I have loadjson method to fetch data from network:
private void loadJson(Consumer<List<Comments>> finished) {
Threading.async(() -> fetchingServer(),finished,null);
}
private List<Comments> fetchingServer() {
JsonplaceholderService service =
ServiceGenerator.createService(JsonplaceholderService.class);
try {
return service.getCommentsRx().call();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
but i got error in fetchingServer method.
java.lang.IllegalArgumentException: Unable to create call adapter for java.util.concurrent.Callable>
for method JsonplaceholderService.getCommentsRx
Retrofit doesn't have adapters for Callable and you can't use it in your #GET method.
You can use:
RxJava2 Observable, Flowable, Single, Completable & Maybe,
Java 8 CompletableFuture
Retrofit Call
So, you can do something like this:
#GET("comments")
Observable<List<Comments>> getCommentsRx(); //rx observable, not java.util.observable
In your client:
service.getCommentsRx()
.subscribeOn(scheduler)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(finished, onError)
I'm using RxJava inside an evernote job to send some data to API. This code was working just fine till now. The problem is somewhere in the .filter I think as it isn't even getting to getApiService().createReport(requestModel) method unless there are photos in the model (then the report.getImages() is not null.
public static Observable<Report> createReport(CreateReportModel model) {
return Observable.just(model)
.filter(report -> report.getImages() != null)
.flatMap(report -> {
return Observable.from(report.getImages())
.map(photoModel -> {
return photoModel;
})
.filter(photoModel -> photoModel.hasImage())
.filter(photoModel -> photoModel.getImage().exists())
.flatMap(photoModel -> uploadFile(photoModel)).toList();
})
.map(photoModels -> model)
.flatMap(requestModel -> {
return getApiService().createReport(requestModel)
.map(response -> {
return response;
});
});
}
This function is called inside this code
Observable<PalletReport> report = createReport(model);
report.subscribe(new Subscriber<PalletReport>() {
#Override
public void onCompleted() {
resultHolder.setResult(Result.SUCCESS);
#Override
public void onError(Throwable e) {
Timber.d(e, "Upload Error");
resultHolder.setResult(Result.RESCHEDULE);
}
#Override
public void onNext(PalletReport model) {
Timber.d("On Next " + model);
}
});
And here it goes to Result.SUCCESS but the response isn't get and the report isn't create on back end. My concern is that this code was working just fine a few days ago, and without any changes it stopped.
[EDIT]
I have this function that is called inside the first flatMap, and it's used to send the photos.
#NonNull
private static Observable<? extends CreatePalletPhotoModel> uploadPalletFile(CreatePalletPhotoModel photo) {
MultipartBody.Part fileBody = Paperless.createFileBody(photo.getImage());
return Paperless.getApiService().uploadPalletPhoto(fileBody)
.map(upload -> {
photo.setPalletStatus(upload.getPalletStatus());
photo.setImage(upload.getImage());
return photo;
});
}
If there are no reports after filter nothing will get executed. Consider removing
.map(photoModels -> model)
and just end the first observable there (you would need to subscribe to it) and start again with
Observable.just(model).flatMap(requestModel -> {
return getApiService().createReport(requestModel)
.map(response -> {
return response;
});
});
that will ensure that getApiService call is always executed.
I'm using Rx for calling our API with Retrofit. At some point I need to call our API, wait for response 1, extract some metadata from it and then call API again waiting for response 2. After I have response 2 I can emit my Observable. My problem is, I don't know how to:
Make a call 2 and emit only after I have response 2
Here are my functions from the class that should emit Model Observable. Method get2 doesn't have to be visible for outside world.
public Observable<Model> get1(String slug) {
return api1
.getInfo(slug)
.subscribeOn(Schedulers.io())
.map(resonse1 -> {
String metadata = response1.getMetadata();
//Make call2 with metadata
//call(2)
Model model = response1.getModel();
model.setInfo(/*Info from call2*/)
return model;
})
.observeOn(AndroidSchedulers.mainThread());
}
private Observable<Info> get2(String metadata) {
return api2.getInfo(new InfoAsset(metadata))
.subscribeOn(Schedulers.io())
.map(response2 -> {
return response2.getInfo;
})
.observeOn(AndroidSchedulers.mainThread());
}
Instead of map use flatMap:
.flatMap(response1 -> {
String metadata = response1.getMetadata();
return get2(metadata)
.map(info -> {
Model model = response1.getModel();
model.setInfo(info);
return model;
});
})
...
Be careful though because you are using mutable objects across threads so you may have visibility problems. Consider using immutable objects or ensure changes are synchronized.
Use nested flatMaps, and don't use observeOn unless you want to do thread hopping:
private Observable<Info> get2(String metadata) {
return api2.getInfo(new InfoAsset(metadata))
.subscribeOn(Schedulers.io())
.map(response2 -> {
return response2.getInfo;
});
// no ObserveOn here.
}
public Observable<Model> get1(String slug) {
return api1
.getInfo(slug)
.subscribeOn(Schedulers.io())
.flatMap (response1 -> {
Model model = response1.getModel();
return get2(response1.getMetadata())
.map(response2 -> {
model.setInfo(response2);
return model;
});
);
});
}
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();
...
}