使用TestCoroutineDispatcher、TestCoroutineScope或Delay
在测试生产代码中创建的 Kotlin 协程中,可以使用 TestCoroutineDispatcher、TestCoroutineScope 或 Delay 来处理delay
。
实现
在本例中,正在测试 SomeViewModel 的视图状态。在 ERROR
状态下,发出一个带有错误值为 true 的视图状态。在定义的 Snackbar 时间长度过去后,使用 delay
发出一个新的视图状态,其中错误值设置为 false。
SomeViewModel.kt
private fun loadNetwork() {
repository.getData(...).onEach {
when (it.status) {
LOADING -> ...
SUCCESS ...
ERROR -> {
_viewState.value = FeedViewState.SomeFeedViewState(
isLoading = false,
feed = it.data,
isError = true
)
delay(SNACKBAR_LENGTH)
_viewState.value = FeedViewState.SomeFeedViewState(
isLoading = false,
feed = it.data,
isError = false
)
}
}
}.launchIn(coroutineScope)
}
有许多方法可以处理delay
。使用advanceUntilIdle
是不错的选择,因为它不需要指定硬编码长度。而且,如果像 Craig Russell 所述在这里注入 TestCoroutineDispatcher,那么这将由 ViewModel 内部使用的相同调度程序处理。
SomeTest.kt
private val testDispatcher = TestCoroutineDispatcher()
private val testScope = TestCoroutineScope(testDispatcher)
testDispatcher.advanceUntilIdle()
这些也可以工作:
testScope.advanceUntilIdle()
testDispatcher.delay(SNACKBAR_LENGTH)
delay(SNACKBAR_LENGTH)
testDispatcher.resumeDispatcher()
testScope.resumeDispatcher()
testDispatcher.advanceTimeBy(SNACKBAR_LENGTH)
testScope.advanceTimeBy(SNACKBAR_LENGTH)
没有处理延迟的错误
kotlinx.coroutines.test.UncompletedCoroutinesError: 在拆卸期间未完成协程。确保测试时所有协程都已完成或取消。
at kotlinx.coroutines.test.TestCoroutineDispatcher.cleanupTestCoroutines(TestCoroutineDispatcher.kt:178)
at app.topcafes.FeedTest.cleanUpTest(FeedTest.kt:127)
at app.topcafes.FeedTest.access$cleanUpTest(FeedTest.kt:28)
at app.topcafes.FeedTest$topCafesTest$1.invokeSuspend(FeedTest.kt:106)
at app.topcafes.FeedTest$topCafesTest$1.invoke(FeedTest.kt)
at kotlinx.coroutines.test.TestBuildersKt$runBlockingTest$deferred$1.invokeSuspend(TestBuilders.kt:50)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.test.TestCoroutineDispatcher.dispatch(TestCoroutineDispatcher.kt:50)
at kotlinx.coroutines.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:288)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt:91)
at kotlinx.coroutines.BuildersKt.async(Unknown Source)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:84)
at kotlinx.coroutines.BuildersKt.async$default(Unknown Source)
at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:49)
at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:80)
at app.topcafes.FeedTest.topCafesTest(FeedTest.kt:41)
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.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.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter