【问题标题】:Only the first test passes with TestScheduler when running multiple tests (Kotlin)运行多个测试时,只有第一个测试通过 TestScheduler (Kotlin)
【发布时间】:2017-12-27 18:08:08
【问题描述】:

我正在尝试在 Kotlin 中使用 RxJava 在 MVP 架构中测试我的演示者。我创建了一个测试规则,用TestScheduler 替换通常的调度程序,以便能够测试异步请求:

class TestSchedulerRule : TestRule {

    val testScheduler = TestScheduler()

    override fun apply(base: Statement, d: Description): Statement {
        return object : Statement() {
            @Throws(Throwable::class)
            override fun evaluate() {
                RxJavaPlugins.setInitIoSchedulerHandler { testScheduler }
                RxJavaPlugins.setInitComputationSchedulerHandler { testScheduler }
                RxJavaPlugins.setInitNewThreadSchedulerHandler { testScheduler }
                RxJavaPlugins.setInitSingleSchedulerHandler { testScheduler }
                RxAndroidPlugins.setInitMainThreadSchedulerHandler { testScheduler }

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

这是我的测试课。问题是每个测试都通过了,如果我单独运行它们,但如果我运行整个套件,只有第一个通过。 TestScheduler 应该有问题,因为如果我使用即时调度程序,它就可以工作。

class UserListPresenterTest {

    @Rule @JvmField
    val testSchedulerRule = TestSchedulerRule()

    @Mock
    lateinit var mockGetUsers: GetUsers

    @Mock
    lateinit var mockView: UserListView

    lateinit var userListPresenter: UserListPresenter

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        userListPresenter = UserListPresenter(mockGetUsers)
    }

    @Test
    fun testGetUsers_errorCase_showError() {
        // Given
        val error = "Test error"
        val single: Single<List<UserViewModel>> = Single.create {
            emitter -> emitter?.onError(Exception(error))
        }

        // When
        `when`(mockGetUsers.execute(Mockito.anyInt(), Mockito.anyBoolean())).thenReturn(single)

        userListPresenter.attachView(mockView)
        userListPresenter.getUsers()

        testSchedulerRule.testScheduler.triggerActions()

        // Then
        verify(mockView).hideLoading()
        verify(mockView).showError()
    }

    @Test
    fun testGetUsers_successCaseFirstPage_clearList() {
        // Given
        val users = listOf(UserViewModel(1, "Name", 1000, ""))
        val single: Single<List<UserViewModel>> = Single.create {
            emitter -> emitter?.onSuccess(users)
        }

        // When
        `when`(mockGetUsers.execute(Mockito.anyInt(), Mockito.anyBoolean())).thenReturn(single)

        userListPresenter.attachView(mockView)
        userListPresenter.getUsers()

        testSchedulerRule.testScheduler.triggerActions()

        // Then
        verify(mockView).clearList()
    }
}

这是我在控制台中遇到的错误:

Wanted but not invoked:
mockView.hideLoading();
-> at com.example.tamaskozmer.kotlinrxexample.UserListPresenterTest.testGetUsers_errorCase_showError(UserListPresenterTest.kt:57)
Actually, there were zero interactions with this mock.

    at com.example.tamaskozmer.kotlinrxexample.UserListPresenterTest.testGetUsers_errorCase_showError(UserListPresenterTest.kt:57)
    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 com.example.tamaskozmer.kotlinrxexample.TestSchedulerRule$apply$1.evaluate(TestSchedulerRule.kt:28)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    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:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    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.AppMainV2.main(AppMainV2.java:131)

UserListPresenter 类

class UserListPresenter(private val getUsers: GetUsers) : BasePresenter<UserListView>() {

    private val offset = 5

    private var page = 1
    private var loading = false

    fun getUsers(forced: Boolean = false) {
        loading = true
        if (forced) {
            resetPaging()
        }
        getUsers.execute(page, forced)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    users ->
                    loading = false
                    if (page == 1) {
                        view?.clearList()
                    }
                    view?.addUsersToList(users)
                    view?.hideLoading()
                    page++
                },
                {
                    loading = false
                    view?.showError()
                    view?.hideLoading()
                })
    }

    private fun resetPaging() {
        page = 1
    }

    fun onScrollChanged(lastVisibleItemPosition: Int, totalItemCount: Int) {
        if (!loading) {
            if (lastVisibleItemPosition >= totalItemCount - offset) {
                getUsers()
            }
        }

        if (loading && lastVisibleItemPosition >= totalItemCount) {
            view?.showLoading()
        }
    }
}

【问题讨论】:

  • init 挂钩会影响 Schedulers 类的首次创建,因此无法重置。您应该改写动态的setComputationSchedulerHandler 和 co。
  • 我尝试先这样做,但那样我什至无法单独运行测试。我收到了这个错误:java.lang.NoClassDefFoundError: Could not initialize class io.reactivex.android.schedulers.AndroidSchedulers
  • 是的,AndroidScheduler 是另一种动物。将其保留为 RxAndroidPlugins.setInitMainThreadSchedulerHandler,但按照我的建议保留其余部分。
  • 刚试过。它产生相同的结果,只有第一个测试通过。
  • 什么是错误堆栈跟踪,包括错误类型? (你的帖子删掉了异常类型)

标签: android unit-testing kotlin rx-java


【解决方案1】:

刚刚解决了同样的问题。您应该定义您的调度程序,而不是在规则中指定它们。 RxAndroidPlugins 将保留第一个 TestScheduler 实例,之后 TestRule.apply 将放置另一个实例进行新测试。

将设置调度程序处理程序放在伴随对象中会有所帮助

companion object {

    val testScheduler: TestScheduler

    init {
        testScheduler = TestScheduler()
        RxAndroidPlugins.setInitMainThreadSchedulerHandler { _ -> testScheduler }
        RxJavaPlugins.setInitComputationSchedulerHandler { _ -> testScheduler }
        RxJavaPlugins.setInitIoSchedulerHandler { _ -> testScheduler }

    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-12-08
    • 1970-01-01
    • 1970-01-01
    • 2014-12-12
    • 2017-10-19
    • 1970-01-01
    • 1970-01-01
    • 2013-10-30
    相关资源
    最近更新 更多