2

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.

R. Zagórski
  • 20,020
  • 5
  • 65
  • 90

1 Answers1

4

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.

AndroidEx
  • 15,524
  • 9
  • 54
  • 50
  • Still same error using `new TestScheduler() ` as constructor param. – iagocanalejas Feb 21 '17 at 13:11
  • 1
    @iagocanalejas sorry if I wasn't clear. Please try using `Schedulers.trampoline()` which would be the current thread a unit test is running on. A `TestScheduler` is not enough. – AndroidEx Feb 21 '17 at 13:14
  • I am searching this issue since 2 days but now things more clear for me. I ended up using `TestScheduler` and now I am using `io.reactivex.schedulers.Schedulers.trampoline();` which returns `io.reactivex.Scheduler` object. And works very well with **RxJava 2.0.8**. Thanks – Yasin Kaçmaz Apr 21 '17 at 13:18