Hot questions for Using Mockito in rx android

Question:

I'm creating unit tests for my RxJava observables in Android.

I want to chain return values from a mock and simulate error/resume values in a observable.

I'm doing this:

when(repository.getObservable())
            .thenReturn(Observable.error(new Exception()))
            .thenReturn(Observable.just(driver));

My observable:

return repository.getObservable()
            .retryWhen(observale -> {
                 return observable
                        .zipWith(Observable.range(1, 3), Pair::create)
                        .flatMap(o -> {
                             if (o.second < count) {
                               return Observable.timer(1000, TimeUnit.MILLISECONDS);
                             }
                             return Observable.error(o.first);
            })))

But I only receive the Observable.error(new Exception()), even calling the retryWhen method 3+ times.

Did somebody know how can I test the re-emission of a different observable to test the retryWhen operator?

Thanks!


Answer:

retryWhen() won't call repository.getObservable() a second time. Instead, it takes the Observable.error() that was returned the first time and resubscribes to it.

In order to use retryWhen() in this way, you'd have to return a single Observable that returns an error the first time it's subscribed to and does not do so in subsequent subscriptions. For example, you could do something like this:

Observable.defer(new Func0<Observable<String>>() {
  boolean hasBeenSubscribedTo = false;

  @Override public Observable<String> call() {
    if (!hasBeenSubscribedTo) {
      hasBeenSubscribedTo = true;
      return Observable.error(new Exception());
    }
    return Observable.just("Hello, world!");
  }
})
.retryWhen(/* ...etc... */)

Question:

I am testing this code.

service.getProducts()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber<Result<Catalog<SoajsProductPreview>>>() {

                @Override
                public void onError(Throwable e) {
                    view.showErrorView(e);
                }

                @Override
                public void onNext(Result<Product>  products) {
                   view.showProducts(products)
                }

                @Override
                public void onCompleted() {}
            });

Testing that view.showProducts() the mocked service returns results works fine. I do

when(service.getProducts().thenReturn(someObservable);

Now I want to test that view.ShowErrorView() is called when the service throws an error but I can't find a way to do that:

Obviously the following doesn't compile

when(service.getProducts().thenReturn(someException); 

And this throws an exception immediately but doesn't call the Subscriber's onError method

when(service.getProducts().thenReturn(someException); 

How can I get Subscriber.onError() called?


Answer:

when(service.getProducts().thenReturn(Observable.error(someException))

should work. See the documentation starting here.

Question:

please help me this when i run all the class test methods together the test fails in a method although this method when i run it alone it succeed

public class RealEstatesListPresenterTest {
RealEstatesListPresenter mRealEstatesListPresenter;
@Mock
private RealEstateListBusiness mRealEstateListBusiness;
@Mock
private RealEstatesListContract.View mRealEstatesView;

@BeforeClass
public static void setUpClass() {
    RxAndroidPlugins.setInitMainThreadSchedulerHandler(__ -> Schedulers.trampoline());
}

@Before
public void setupTasksPresenter() {
    MockitoAnnotations.initMocks(this);
    mRealEstatesListPresenter = new RealEstatesListPresenter(mRealEstatesView);
    mRealEstatesListPresenter.setmRealEstateListBusiness(mRealEstateListBusiness);
}

@Test
public void testWhenGetAllRealEstates_ProgressISDisplayed() {
    when(mRealEstateListBusiness.getAllRealEstates()).thenReturn(Observable.create(sub -> {
        sub.onNext(new ArrayList<>());
        sub.onComplete();
    }));
    mRealEstatesListPresenter.getAllRealEstates();
    verify(mRealEstatesView, times(1)).showLoading();
}

@Test
public void testWhenGetAllRealEstatesSuccess_ProgressISHidden() {
    when(mRealEstateListBusiness.getAllRealEstates()).thenReturn(Observable.create(sub -> {
        sub.onNext(new ArrayList<>());
        sub.onComplete();
    }));
    mRealEstatesListPresenter.getAllRealEstates();
    verify(mRealEstatesView, times(1)).hideLoading();
}

@Test
public void testWhenGetAllRealEstatesError_ProgressISHidden() {
    when(mRealEstateListBusiness.getAllRealEstates()).thenReturn(Observable.create(sub -> {
        sub.onError(new Throwable());
    }));
    mRealEstatesListPresenter.getAllRealEstates();
    verify(mRealEstatesView, times(1)).hideLoading();
}

@AfterClass
public static void tearDownClass() {
    RxAndroidPlugins.reset();
}}

when i run all the tests together the first two methods pass but the last one fail (testWhenGetAllRealEstatesError_ProgressISHidden) but when i run it alone it pass.

and this is the presenter code

public class RealEstatesListPresenter implements RealEstatesListContract.Presenter {

private RealEstatesListContract.View mView;
private RealEstateListBusiness mRealEstateListBusiness;
private CompositeDisposable mSubscriptions;

@Inject
public RealEstatesListPresenter(RealEstatesListContract.View view) {
    this.mView = view;
    mSubscriptions = new CompositeDisposable();
}

@Inject
public void setmRealEstateListBusiness(RealEstateListBusiness mRealEstateListBusiness) {
    this.mRealEstateListBusiness = mRealEstateListBusiness;
}

@Inject
public void setupListeners() {
    mView.setPresenter(this);
}

@Override
public void unSubscribe() {
    mSubscriptions.clear();
}

@Override
public void getAllRealEstates() {
    mView.showLoading();
    mSubscriptions.add(mRealEstateListBusiness.getAllRealEstates().observeOn(AndroidSchedulers.
            mainThread()).subscribeOn(Schedulers.io()).subscribe((realEstatesItems) -> {
        mView.hideLoading();
        mView.showAllRealEstates(realEstatesItems);
    }, throwable -> {
        mView.hideLoading();
        mView.showErrorMessage(throwable.getMessage());
    }));
}

}


Answer:

i found the problem it is in RX Scheulers This error occurs because the default scheduler returned by AndroidSchedulers.mainThread() is an instance of LooperScheduler and relies on Android dependencies that are not available in JUnit tests.

We can avoid this issue by initializing RxAndroidPlugins with a different Scheduler before the tests are run. You can do this inside of a @BeforeClass so the final class would be like this

public class RealEstatesListPresenterTest {
private RealEstatesListPresenter mRealEstatesListPresenter;
@Mock
private RealEstateListBusiness mRealEstateListBusiness;
@Mock
private RealEstatesListContract.View mRealEstatesView;

@BeforeClass
public static void setUpClass() {
    Scheduler immediate = new Scheduler() {
        @Override
        public Worker createWorker() {
            return new ExecutorScheduler.ExecutorWorker(Runnable::run);
        }
    };

    RxJavaPlugins.setInitIoSchedulerHandler(scheduler -> immediate);
    RxJavaPlugins.setInitComputationSchedulerHandler(scheduler -> immediate);
    RxJavaPlugins.setInitNewThreadSchedulerHandler(scheduler -> immediate);
    RxJavaPlugins.setInitSingleSchedulerHandler(scheduler -> immediate);
    RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> immediate);    }

@Before
public void setupTasksPresenter() {
    MockitoAnnotations.initMocks(this);
    mRealEstatesListPresenter = new RealEstatesListPresenter(mRealEstatesView);
    mRealEstatesListPresenter.setmRealEstateListBusiness(mRealEstateListBusiness);
}

@Test
public void testWhenGetAllRealEstates_ProgressISDisplayed() {
    when(mRealEstateListBusiness.getAllRealEstates()).thenReturn(Observable.create(sub -> {
        sub.onNext(new ArrayList<>());
        sub.onComplete();
    }));
    mRealEstatesListPresenter.getAllRealEstates();
    verify(mRealEstatesView, times(1)).showLoading();
}

@Test
public void testWhenGetAllRealEstatesSuccess_ProgressISHidden() {
    when(mRealEstateListBusiness.getAllRealEstates()).thenReturn(Observable.create(sub -> {
        sub.onNext(new ArrayList<>());
        sub.onComplete();
    }));
    mRealEstatesListPresenter.getAllRealEstates();
    verify(mRealEstatesView, times(1)).hideLoading();
}

@Test
public void testWhenGetAllRealEstatesError_ProgressISHidden() {
    when(mRealEstateListBusiness.getAllRealEstates()).thenReturn(Observable.create(sub -> {
        sub.onError(new Throwable());
    }));
    mRealEstatesListPresenter.getAllRealEstates();
    verify(mRealEstatesView, times(1)).hideLoading();
}

@Test
public void testWhenGetAllRealEstatesError_ErrorMessageDisplayed() {
    when(mRealEstateListBusiness.getAllRealEstates()).thenReturn(Observable.create(sub -> {
        sub.onError(new Throwable(""));
    }));
    mRealEstatesListPresenter.getAllRealEstates();
    verify(mRealEstatesView, times(1)).showErrorMessage("");
}

@After
public void teardown() {
    Mockito.reset(mRealEstateListBusiness);
    Mockito.reset(mRealEstatesView);
    Mockito.validateMockitoUsage();
}

@AfterClass
public static void tearDownClass() {
    RxJavaPlugins.reset();
    RxAndroidPlugins.reset();    }}

Question:

Hi i want to test an Android presenter using mockito and i am having a lot of trouble. Here is the code i want to test:

public class SignUpPresenter extends BasePresenter {
    private static final String TAG = SignUpPresenter.class.getSimpleName();

    private final AuthRepository mAuthRepository;
    private final SignUpView mView;

    public SignUpPresenter(@NonNull SignUpView view, @NonNull AuthRepository authRepository) {
        this.mAuthRepository = authRepository;
        this.mView = view;
    }

    public void signUp(@NonNull String username, @NonNull String password, @NonNull String email) {
        mCompositeDisposable.add(mAuthRepository.signUp(username, password, email)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(new DisposableSingleObserver<TokenWrapper>() {
                    @Override
                    public void onSuccess(TokenWrapper t) {
                        Log.i(TAG, "Signed Up: " + t.getUser().getUsername());
                        mView.onSuccessSignUp(t.getUser(), t.getToken());
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e(TAG, "Sign Up: " + e.getMessage());
                        mView.onErrorSignUp(e.getMessage());
                    }
                }));
    }
}

The test code:

public class SignUpPresenterTest {

    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Mock public SignUpView mView;
    @Mock public AuthRepository mRepository;

    SignUpPresenter mPresenter;

    User mUser = new User();

    @Before
    public void setUp() {
        mPresenter = new SignUpPresenter(mView, mRepository);
    }

    @Test
    public void onSuccessSignUpTest() throws InterruptedException {
        TokenWrapper token = new TokenWrapper(mUser, null);
        when(mRepository.signUp("Username", "password", "asdf@msais.com"))
                .thenReturn(Single.just(token));

        mPresenter.signUp("Username", "password", "asdf@msais.com");

        verify(mView).onSuccessSignUp(mUser, null);
    }

    @Test
    public void onSignUpErrorTest() {
        when(mRepository.signUp("Username", "password", "asdf@msais.com"))
                .thenReturn(Single.error(new Throwable("Error")));

        mPresenter.signUp("Username", "password", "asdf@msais.com");

        verify(mView).onErrorSignUp("Error");
    }

}

And the exception:

    Wanted but not invoked:
mView.onSuccessSignUp(
    com.andiag.trainingsquad.models.entities.User@d6e7bab,
    null
);
-> at com.andiag.trainingsquad.SignUpPresenterTest.onSuccessSignUpTest(SignUpPresenterTest.java:51)
Actually, there were zero interactions with this mock.

Wanted but not invoked:
mView.onSuccessSignUp(
    com.andiag.trainingsquad.models.entities.User@d6e7bab,
    null
);
-> at com.andiag.trainingsquad.SignUpPresenterTest.onSuccessSignUpTest(SignUpPresenterTest.java:51)
Actually, there were zero interactions with this mock.

    at com.andiag.trainingsquad.SignUpPresenterTest.onSuccessSignUpTest(SignUpPresenterTest.java:51)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.mockito.internal.junit.JUnitRule$1.evaluateSafely(JUnitRule.java:63)
    at org.mockito.internal.junit.JUnitRule$1.evaluate(JUnitRule.java:43)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:119)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

Thanks for the help.


Answer:

I think the problem is in the background thread involved (.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())): the unit tests complete before the computation under test does.

You should try mapping your threads to the current one in the unit tests with a test rule. Something like that (my snippet is in Kotlin but hopefully the general idea is clear):

class SchedulersOverrideRule(private val scheduler: Scheduler = Schedulers.trampoline()) : TestRule {

    override fun apply(base: Statement, description: Description?): Statement {
        return object : Statement() {
            override fun evaluate() {
                RxJavaPlugins.setIoSchedulerHandler { scheduler }
                RxJavaPlugins.setComputationSchedulerHandler { scheduler }
                RxJavaPlugins.setNewThreadSchedulerHandler { scheduler }
                RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler }
                RxAndroidPlugins.setMainThreadSchedulerHandler { scheduler }

                try {
                    base.evaluate()
                } finally {
                    RxJavaPlugins.reset()
                    RxAndroidPlugins.reset()
                }
            }
        }
    }
}

And apply this rule the same way MockitoRule is applied in your tests.

Question:

I am trying to test that an API call is scheduled on the right scheduler and observes on the main thread.

@RunWith(PowerMockRunner.class)
@PrepareForTest({Observable.class, AndroidSchedulers.class})
public class ProductsPresenterTest {

    private  ProductsPresenter presenter;
    @Before
    public void setUp() throws Exception{
        presenter = spy(new ProductsPresenter(mock(SoajsRxRestService.class)));
    }


    @Test
    public void testShouldScheduleApiCall(){
        Observable productsObservable = mock(Observable.class);
        CatalogSearchInput catalogSearchInput = mock(CatalogSearchInput.class);
        when(presenter.soajs.getProducts(catalogSearchInput)).thenReturn(productsObservable);

        /* error here*/ 
        when(productsObservable.subscribeOn(Schedulers.io())).thenReturn(productsObservable);
        when(productsObservable.observeOn(AndroidSchedulers.mainThread())).thenReturn(productsObservable);
        presenter.loadProducts(catalogSearchInput);

        //verify if all methods in the chain are called with correct arguments
        verify(presenter.soajs).getProducts(catalogSearchInput);
        verify(productsObservable).subscribeOn(Schedulers.io());
        verify(productsObservable).observeOn(AndroidSchedulers.mainThread());
        verify(productsObservable).subscribe(Matchers.<Subscriber<Result<Catalog<SoajsProductPreview>>>>any());
    }
}

The line

when(productsObservable.subscribeOn(Schedulers.io())).thenReturn(productsObservable);

throws the following exception, and I don't understand why since productObservable is a mock. Any idea or similar experience?

org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
   Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.

Answer:

The problem is due Observable::subscribeOn being a final method, which Mockito can't mock. One possible solution is to use Powermock:

@RunWith(PowerMockRunner.class)
@PrepareForTest(Observable.class)
public class MockTest {
    @Test
    public void test() {
        Observable productsObservable = PowerMockito.mock(Observable.class);

        when(productsObservable.subscribeOn(null)).thenReturn(productsObservable);
        productsObservable.subscribeOn(null);
        verify(productsObservable).subscribeOn(null);
    }
}