问题
viewModelScope
使用 Kotlin 协程为 Android 单元测试注入的最佳策略是什么?当 CoroutineScope 被注入到 ViewModel 进行单元测试时,
flowOn
即使在生产代码中不需要 CoroutineDispatcher 是否也应该被注入和定义?
flowOn
在这个用例中,生产代码中不需要,因为 RetrofitDispatchers.IO
在SomeRepository.kt中处理线程,并在默认情况下viewModelScope
返回数据。Dispathers.Main
预期的
对保存在 Kotlin Flow 值中的 Android 的 ViewModel 视图状态值运行单元测试。
观察到的
带有主调度程序的模块未能初始化。对于测试,可以使用来自 kotlinx-coroutines-test 模块的 Dispatchers.setMain
单元测试在 CoroutineScope 被硬编码的第一次出现时失败。viewModelScope
被利用,以便启动的协程将维持 ViewModel 的生命周期。但是,viewModelScope
它是从 ViewModel 内部创建的,与可以在 ViewModel 外部定义并作为参数传入的 CoroutineDispatcher 相比,这使得注入更加复杂。
执行
SomeViewModel.kt
fun bindIntents(view: FeedView) {
view.initStateIntent().onEach {
initState(view)
}.launchIn(viewModelScope)
}
SomeTest.kt
@ExperimentalCoroutinesApi
class SomeTest : BeforeAllCallback, AfterAllCallback {
private val testDispatcher = TestCoroutineDispatcher()
private val testScope = TestCoroutineScope(testDispatcher)
private val repository = mockkClass(FeedRepository::class)
private var loadNetworkIntent = MutableStateFlow<LoadNetworkIntent?>(null)
override fun beforeAll(context: ExtensionContext?) {
// Set Coroutine Dispatcher.
Dispatchers.setMain(testDispatcher)
}
override fun afterAll(context: ExtensionContext?) {
Dispatchers.resetMain()
// Reset Coroutine Dispatcher and Scope.
testDispatcher.cleanupTestCoroutines()
testScope.cleanupTestCoroutines()
}
@Test
fun topCafesPoc() = testDispatcher.runBlockingTest {
coEvery {
repository.getInitialCafes(any())
} returns mockGetInitialCafes(mockCafesList, SUCCESS)
val viewModel = FeedViewModel(repository)
viewModel.bindIntents(object : FeedView {
@ExperimentalCoroutinesApi
override fun initStateIntent() = MutableStateFlow(true)
@ExperimentalCoroutinesApi
override fun loadNetworkIntent() = loadNetworkIntent.filterNotNull()
override fun render(viewState: FeedViewState) {
// TODO: Test viewState
}
})
loadNetworkIntent.value = LoadNetworkIntent(true)
// TODO
// assertEquals(4, 2 + 2)
}
}
注意:最终版本中将使用 JUnit 5 测试扩展。
完整的错误日志
线程“main @coroutine#1”java.lang.IllegalStateException 中的异常:带有 Main 调度程序的模块未能初始化。对于测试,来自 kotlinx-coroutines-test 模块的 Dispatchers.setMain 可以在 kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.missing(MainDispatchers.kt:113) 在 kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.isDispatchNeeded(MainDispatchers.kt:91) 上使用kotlinx.coroutines.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:285) 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.launch(Builders. 68)在 com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) 在 com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230) 在 com.intellij.rt.junit .JUnitStarter.main(JUnitStarter.java:58) 原因:java.lang.RuntimeException:android.os.Looper 中的方法 getMainLooper 未模拟。看http://g.co/androidstudio/not-mocked详情。在 android.os.Looper.getMainLooper(Looper.java) 在 kotlinx.coroutines.android.AndroidDispatcherFactory.createDispatcher(HandlerDispatcher.kt:55) 在 kotlinx.coroutines.android.AndroidDispatcherFactory.createDispatcher(HandlerDispatcher.kt:52) 在 kotlinx。 coroutines.internal.MainDispatchersKt.tryCreateDispatcher(MainDispatchers.kt:57) at kotlinx.coroutines.test.internal.TestMainDispatcher.getDelegate(MainTestDispatcher.kt:19) at kotlinx.coroutines.test.internal.TestMainDispatcher.getImmediate(MainTestDispatcher.kt: 32) 在 androidx.lifecycle.ViewModelKt.getViewModelScope(ViewModel.kt:42) ... 40 更多线程“main @coroutine#1”java.lang.IllegalStateException 中的异常:主调度程序的模块未能初始化。用于测试调度程序。com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58) 上的 prepareStreamsAndStart(JUnitStarter.java:230) 原因:java.lang.RuntimeException:android.os.Looper 中的方法 getMainLooper 未模拟。看http://g.co/androidstudio/not-mocked了解详情。在 android.os.Looper.getMainLooper(Looper.java) 在 kotlinx.coroutines.android.AndroidDispatcherFactory.createDispatcher(HandlerDispatcher.kt:55) 在 kotlinx.coroutines.android.AndroidDispatcherFactory.createDispatcher(HandlerDispatcher.kt:52) 在 kotlinx。 coroutines.internal.MainDispatchersKt.tryCreateDispatcher(MainDispatchers.kt:57) at kotlinx.coroutines.test.internal.TestMainDispatcher.getDelegate(MainTestDispatcher.kt:19) at kotlinx.coroutines.test.internal.TestMainDispatcher.getImmediate(MainTestDispatcher.kt: 32) 在 androidx.lifecycle.ViewModelKt.getViewModelScope(ViewModel.kt:42) 在 app.topcafes.feed.viewmodel.FeedViewModel.bindIntents(FeedViewModel.kt:38) ... 39 更多