android - RxJava 在 Android 中测试 Presenter 时遇到的问题

标签 android mockito rx-android rx-java2 reactive

您好,我想使用mockito 测试Android Presenter,但遇到了很多麻烦。这是我要测试的代码:

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());
                    }
                }));
    }
}

测试代码:

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", "<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a9c8dacdcfe9c4dac8c0da87cac6c4" rel="noreferrer noopener nofollow">[email protected]</a>"))
                .thenReturn(Single.just(token));

        mPresenter.signUp("Username", "password", "<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="cdacbea9ab8da0beaca4bee3aea2a0" rel="noreferrer noopener nofollow">[email protected]</a>");

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

    @Test
    public void onSignUpErrorTest() {
        when(mRepository.signUp("Username", "password", "<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4524362123052836242c366b262a28" rel="noreferrer noopener nofollow">[email protected]</a>"))
                .thenReturn(Single.error(new Throwable("Error")));

        mPresenter.signUp("Username", "password", "<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="18796b7c7e58756b79716b367b7775" rel="noreferrer noopener nofollow">[email protected]</a>");

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

}

异常(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)

感谢您的帮助。

最佳答案

我认为问题出在涉及的后台线程中(.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())):单元测试在测试计算之前完成确实如此。

您应该尝试使用测试规则将线程映射到单元测试中的当前线程。类似的东西(我的代码 fragment 是用 Kotlin 编写的,但希望总体思路是清晰的):

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()
                }
            }
        }
    }
}

并以与在测试中应用 MockitoRule 相同的方式应用此规则。

关于android - RxJava 在 Android 中测试 Presenter 时遇到的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42367436/

相关文章:

Android Flavors - 每个应用程序的单独可绘制目录?

android - 带有 fragment 的 SearchView

java - 如何对与系统(或 Android)类交互的方法进行单元测试

android - RecyclerView 使用 RxJava 无限滚动

observable - takeFirst 方法的替代方法是什么

android - 将语言环境设置为应用程序小部件

Android:释放时删除未使用的资源

java - Mockito 模拟不适用于此方法。难道我做错了什么?

android - 使用Realm的测试方法

java - 如何以 RxJava2 的方式组织异步调用链