Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Verify interactions in rxjava subscribers

Picture the situation in an MVP pattern where your presenter subscribes to a service returning an observer:

public void gatherData(){
   service.doSomeMagic()
      .observeOn(Schedulers.io())
      .subscribeOn(AndroidSchedulers.mainThread())
      .subscribe(new TheSubscriber());
}

Now the class TheSubscriber calls onNext a method from the view, say:

@Override public void onNext(ReturnValue value) {
  view.displayWhatever(value);
}

Now, in my unit test I would like to verify that when the method gatherData() is called on a non-erroneous situation, the view's method displayWhatever(value) is called.

The question:

Is there a clean way to do this?

Background:

  • I'm using mockito to verify the interactions and a lot more of course
  • Dagger is injecting the entire presenter except for TheSubscriber

What have I tried:

  • Inject the subscriber and mock it in the tests. Looks a bit dirty to me, because if I want to change the way the presenter interacts with the service (Say not Rx) then I need to change a lot of tests and code.
  • Mock the entire service. This was not so bad, but requires me to mock a lot of methods and I didn't quite reach what I wanted.
  • Looked up around the internet, but no one seems to have a clean straight way of doing this

Thanks for the help

like image 761
Fred Avatar asked Apr 26 '26 18:04

Fred


2 Answers

Assuming that you are using interfaces for service and view in a similar manner:

class Presenter{
  Service service;
  View view;

  Presenter(Service service){
    this.service = service;
  }

  void bindView(View view){
    this.view = view;
  }

  void gatherData(){
    service.doSomeMagic()
      .observeOn(Schedulers.io())
      .subscribeOn(AndroidSchedulers.mainThread())
      .subscribe(view::displayValue);
  }
}

It is possible then to provide mock to control and verify behaviour:

@Test void assert_that_displayValue_is_called(){
  Service service = mock(Service.class);
  View view = mock(View.class);
  when(service.doSomeMagic()).thenReturn(Observable.just("myvalue"));
  Presenter presenter = new Presenter(service);
  presenter.bindView(view);

  presenter.gatherData();

  verify(view).displayValue("myvalue");
}
like image 184
Sergii Pechenizkyi Avatar answered Apr 28 '26 07:04

Sergii Pechenizkyi


I know its pretty late but may it helps someone, cause i searched pretty long for a solution to your question :D

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 just 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 :)

hope this helps and is somehow understandable :D

like image 43
Makzimalist Avatar answered Apr 28 '26 07:04

Makzimalist



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!