Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Verify that method is called in onNext of RxJava Subscriber

I have the following method that uses a Retrofit service interface to fetch some data from an API, then interacts with a view interface.

@Override
@VisibleForTesting
public void fetchPhotos(@Nullable PhotosService service, @Nullable Scheduler subscribeOn) {
    view.showLoading();

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(Constants.PLACEHOLDER_API_BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build();

    if (service == null) service = retrofit.create(PhotosService.class);
    if (subscribeOn == null) subscribeOn = Schedulers.newThread();

    service.listPhotos()
            .subscribeOn(subscribeOn)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(photoList -> {
                Log.d(TAG, "got photos " + photoList.toString());
                view.unshowLoading();
            }, throwable -> {
                Log.d(TAG, "error " + throwable.toString());
                view.unshowLoading();
                view.displayError(throwable.toString(), v -> fetchPhotos());
            });
}

I want to test that view.unshowLoading() is called in onNext.

Here's my test:

@Test
public void viewUnshowsLoadingAfterFetchingPhotos() {

    PhotosListView view = Mockito.mock(PhotosListView.class);

    PhotosListPresenter presenter = new PhotosListPresenterImpl(view);

    presenter.fetchPhotos(() -> Observable.create(new Observable.OnSubscribe<List<Photo>>() {
        @Override
        public void call(Subscriber<? super List<Photo>> subscriber) {

            subscriber.onNext(new ArrayList<Photo>());

        }
    }), Schedulers.immediate());

    Mockito.verify(view).unshowLoading();


}

I explicitly pass in the Scheduler Schedulers.immediate() to make sure onNext() is called instantly on the subscribing thread.

When I debug through my method though, onNext() is not called. What am I doing wrong or how could I best test this?

EDIT: This article brought me on to something:

If you wish to change the thread on which the operation is performed you can call subscribeOn(). To get back to the main thread use observeOn(AndroidSchedulers.mainThread()). However, notice that whenever you force the operation onto a specific thread, it will always make the subscription asynchronous.

When I omit the

        .subscribeOn(subscribeOn)
        .observeOn(AndroidSchedulers.mainThread())

part, the test works as expected. I've rearranged my method to no call in observeOn() or subscribeOn() when no schedulers are passed in:

public void fetchPhotos(@Nullable PhotosService service, @Nullable Scheduler subscribeOn, @Nullable Scheduler observeOn) {
    view.showLoading();

    if (service == null) service = createService();

    Observable<List<Photo>> observable = service.listPhotos();

    if (subscribeOn != null) observable = observable.subscribeOn(subscribeOn);
    if (observeOn != null) observable = observable.observeOn(observeOn);

    observable.subscribe(photoList -> {
        Log.d(TAG, "got photos " + photoList.toString());
        view.unshowLoading();
    }, throwable -> {
        Log.d(TAG, "error " + throwable.toString());
        view.unshowLoading();
        view.displayError(throwable.toString(), v -> fetchPhotos());
    });
}

Looks a bit clumsy, but works.

Any ideas still welcome :)

like image 341
fweigl Avatar asked Nov 09 '22 01:11

fweigl


1 Answers

The first sample is fine, just inject the ui scheduler and use that. In your test inject something like an immediate scheduler, and in production inject the Android ui scheduler. In general it's better not to hardcode dependencies in your classes, but rather to inject them. This is one of those cases in which dependency injection could have helped.

A note on subscribeOn: you don't need to use it with retrofit as retrofit will perform the operation on a default thread anyway. Also naming schedulers "subscribeOn" and "observeOn" doesn't make much sense, as you might want to use the same scheduler to pass to subscribeOn() and observeOn(). It would be better to give them more meaningful names given what they represent, for example "backgroundScheduler" and "uiScheduler"

like image 174
memoizr Avatar answered Nov 14 '22 23:11

memoizr