3

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

Fred
  • 16,367
  • 6
  • 50
  • 65
  • MVP is such an overloaded term. I think with MVP View is also interface, which can be injected into Presenter. So you can inject Activity/Fragment for app and mockito mock for tests. – Sergii Pechenizkyi Jul 15 '15 at 07:21
  • yes, well the view's injected and mocked. The issue here is how can you easily configure your mocked ``service`` to call the methods of the subscriber that will eventually call the ones from the view. – Fred Jul 15 '15 at 07:26
  • Are you asking about `when(service.doSomeMagic()).thenReturn(Observable.just("yourvalue-with-correct-type"));`? – Sergii Pechenizkyi Jul 15 '15 at 07:44
  • Sometimes the easiest solution is not so obvious. Yes your comment actually helped out. Had to do some extra stuff, but that's related with my app. Do you want to add an answer? – Fred Jul 15 '15 at 10:48

2 Answers2

6

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");
}
Sergii Pechenizkyi
  • 22,227
  • 7
  • 60
  • 71
  • I almost had the test case running. But ended up with` Caused by: java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details. at android.os.Looper.getMainLooper(Looper.java) at io.reactivex.android.schedulers.AndroidSchedulers.(AndroidSchedulers.java:24) ... 32 more`. I checked this https://github.com/ReactiveX/RxAndroid/issues/238 and i still haven't got a way to test RxJava code – Raghunandan Sep 28 '16 at 11:50
  • This is pretty much the same idea that I had, but I keep running into a NullPointerException on the Schedulers.io() line. Then on the subscribe() line if I comment out the schedulers that I'm using - I subscribe using an anonymous inner class/lambda. – Kavi Oct 04 '16 at 12:21
  • I ended up injecting the schedulers as well. At testing time I use for both ``observeOn`` and ``subscribeOn`` ``Schedulers.immediate()``. – Fred Oct 06 '16 at 12:55
  • Check out https://stackoverflow.com/questions/43356314/android-rxjava-2-junit-test-getmainlooper-in-android-os-looper-not-mocked-runt/43356315#43356315 – starkej2 Oct 12 '17 at 15:46
2

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

Makzimalist
  • 53
  • 1
  • 6
  • wow, better late than never! :D yeah your solution is pretty cool. I do that myself now. Basically fast forward some years and more experience and the answer boils down to: inject your schedulers! – Fred Jul 04 '17 at 09:52
  • :D No problem... i raged because of this problem and now its solved and the test are green. good luck for the future :) – Makzimalist Jul 04 '17 at 10:10