【发布时间】: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