我正在为我的Android应用程序编写一个集成测试。我在liveData {...}协同构建器中遇到了一个问题:当我在其中调用withContext {...}函数时,它会切换到给定的上下文(例如Dispatchers.IO),但在返回(到Dispatchers.Main.immediate)之后不会切换回来。
测试结果如下:
WordFragmentIntegrationTest:
@RunWith(RobolectricTestRunner::class)
@Config(application = TestAppWithDaggerComponent::class)
class WordFragmentIntegrationTest {
@get:Rule
val coroutinesRule = CoroutinesRule()
private val mockWebServer = MockWebServer()
@After
fun teardown() {
mockWebServer.shutdown()
}
@Test
fun `should fetch a word from api and populate the view`() = runBlockingTest {
// Mock api response, it works fine.
val response = MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)
.setBody(SAMPLE_API_WORD_JSON)
.setBodyDelay(0, TimeUnit.MILLISECONDS)
mockWebServer.enqueue(response)
mockWebServer.start(8080)
// Launch a Fragment under test. It triggers an api call.
val fragmentScenario = launchFragmentInContainer<WordFragment>()
// Wait for the Fragment to do its work.
fragmentScenario.onFragment { fragmentUnderTest ->
runBlocking {
while (fragmentUnderTest.isLoading) { yield() }
// Do some assertions...
}
}
}
}CoroutinesRule:
class CoroutinesRule : ExternalResource() {
override fun before() {
Dispatchers.setMain(TestCoroutineDispatcher())
}
override fun after() {
Dispatchers.resetMain()
}
}和一段代码,这会导致测试失败。
WordViewModel:
val wordLiveData = liveData {
printCurrentThread("Emitting the first value")
emit(UIState.ShowLoading)
val value = withContext(Dispatchers.IO) {
printCurrentThread("Fetching a value")
loadWordUseCase(wordId)
}
printCurrentThread("Emitting the second value")
emit(value)
}
private fun printCurrentThread(message: String) {
val threadInfo = "Thread: ${Thread.currentThread().id}. UI thread: ${Looper.getMainLooper().thread.id}"
println("$message. $threadInfo")
}它在生产环境中运行良好:
Emitting the first value. Thread: 1. UI thread: 1 # First emitting is on the UI thread.
Fetching a value. Thread: 365. UI thread: 1 # Fetching is on some IO thread.
Emitting the second value. Thread: 1. UI thread: 1 # Switched back to the UI thread.但是在Dispatchers.Main被替换的测试环境中,withContext {...}不会切换回liveData {...}的Dispatchers.Main.immediate,它会导致CoroutineLiveData崩溃,因为它的emit()应该从UI线程中调用。
Emitting the first value. Thread: 11. UI thread: 11 # Emitted on the UI thread.
Fetching a value. Thread: 19. UI thread: 11 # Switched to IO thread.
Emitting the second value. Thread: 19. UI thread: 11 # Didn't switch back, which caused:
Exception in thread "DefaultDispatcher-worker-1 @coroutine#2"
java.lang.IllegalStateException: Cannot invoke setValue on a background thread
at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:462)
at androidx.lifecycle.LiveData.setValue(LiveData.java:304)
...是窃听器还是我做错了什么?也许我搞错了CoroutinesRule
发布于 2020-06-25 14:47:44
这是因为TestCoroutineDispatcher没有像android的Dispatchers.Main那样切换回特定的线程。相反,它的行为类似于Dispatchers.Unconfined。
我不确定这是否适用于Robolectric,但是在单元测试中起作用的是:
将InstantTaskExecutorRule添加到您的测试中,就像您对CoroutinesRule做的那样。例如:
@get:Rule
val executorRule = InstantTaskExecutorRule()这将允许在任何线程上调用LiveData.setValue(...)。
由于emit最终只是委托给setValue,所以不需要调整ViewModel代码。
如果该规则尚未可用,则添加此依赖项:
testImplementation 'androidx.arch.core:core-testing:2.1.0'如果这仍然没有帮助,我想知道如果你不替换主调度员会发生什么
https://stackoverflow.com/questions/62518374
复制相似问题