【发布时间】:2021-11-11 17:41:23
【问题描述】:
我目前正在为一个小型 Android 应用程序编写单元测试,但遇到了一个相当奇怪的错误。
我将 Kotlin 协程与 Retrofit 2 结合使用,向 API 发出简单的 HTTP GET 请求。总体而言,该应用程序按预期运行,我使用 MockWebServer 编写了测试,除了尝试测试来自 API 的错误响应(在某种程度上颇具讽刺意味)之外,它也运行良好。
基本上,当我故意创建错误响应时,调用事物的顺序完全不正常。
下面是有问题的测试代码:
@Test
fun viewModel_loadData_correctErrorHandling() {
mockServer.enqueue(MockResponse().apply {
setResponseCode(500)
})
viewModel.loadModel()
assert(!viewModel.loading)
assert(viewModel.loadingVisibility.value != View.VISIBLE)
assertNotNull(viewModel.currentError)
assert(viewModel.errorVisibility.value == View.VISIBLE)
assertNull(viewModel.model.value)
assert(viewModel.contentVisibility.value != View.VISIBLE)
}
viewModel.loadModel()函数如下:
fun loadModel() {
currentError = null
loading = true
model.value = null
interactor.load(viewModelScope, @MainThread {
loading = false
model.value = it
}, @MainThread {
loading = false
currentError = it
Timber.e(it)
})
}
最后interactor.load函数如下。
fun load(
scope: CoroutineScope,
onSuccess: (List<ConsumableCategory>) -> Unit,
onError: (Throwable) -> Unit
) {
scope.launch {
try {
onSuccess(dataManager.getConsumableCategories())
} catch (t: Throwable) {
onError(t)
}
}
}
dataManager.getConsumableCategories() 只是引用了对我的Retrofit 实例创建的挂起函数的调用。
运行有问题的测试时,我的输出如下:
2021-09-16T20:05:27.648+0200 [DEBUG] [TestEventLogger] loadModel start
2021-09-16T20:05:27.648+0200 [DEBUG] [TestEventLogger] Pre Scope
2021-09-16T20:05:27.672+0200 [DEBUG] [TestEventLogger] Post Scope
2021-09-16T20:05:27.672+0200 [DEBUG] [TestEventLogger] loadModel end
2021-09-16T20:05:27.681+0200 [DEBUG] [TestEventLogger] onError start
2021-09-16T20:05:27.740+0200 [DEBUG] [TestEventLogger]
2021-09-16T20:05:27.740+0200 [DEBUG] [TestEventLogger] com.kenthawkings.mobiquityassessment.ConsumableViewModelTest > viewModel_loadData_correctErrorHandling FAILED
2021-09-16T20:05:27.741+0200 [DEBUG] [TestEventLogger] java.lang.AssertionError: Assertion failed
2021-09-16T20:05:27.741+0200 [DEBUG] [TestEventLogger] at com.kenthawkings.mobiquityassessment.ConsumableViewModelTest.viewModel_loadData_correctErrorHandling(ConsumableViewModelTest.kt:104)
...
不知何故我的onError 块在loadModel 函数完成后被调用。因此,assert(!viewModel.loading) 行失败,因为它在loading 变量在onError 回调中设置为false 之前被调用。我正在使用自定义规则来确保一切同步运行。
@get:Rule
val testInstantTaskExecutorRule = InstantTaskExecutorRule()
@ExperimentalCoroutinesApi
@get:Rule
val mainCoroutineRule = MainCoroutineRule()
我尝试过使用runBlocking 和runBlockingTest(两者都围绕整个测试或仅围绕viewModel.loadModel() 行)并且没有任何区别。我尝试从使用try-catch 切换到使用CoroutineExceptionHandler 并使用kotlin.runCatching,但我总是得到相同的结果。
真正奇怪的部分是成功响应测试按预期工作,并且所有语句都“按顺序”打印。
@Test
fun viewModel_loadData_correctSuccessHandling() {
val reader = MockResponseFileReader("success_response.json")
assertNotNull(reader.content)
mockServer.enqueue(MockResponse().apply {
setResponseCode(200)
setBody(reader.content)
setHeader("content-type", "application/json")
})
viewModel.loadModel()
assert(!viewModel.loading)
assert(viewModel.loadingVisibility.value != View.VISIBLE)
assertNull(viewModel.currentError)
assert(viewModel.errorVisibility.value != View.VISIBLE)
assertNotNull(viewModel.model.value)
assert(viewModel.contentVisibility.value == View.VISIBLE)
}
2021-09-16T20:05:27.542+0200 [DEBUG] [TestEventLogger] loadModel start
2021-09-16T20:05:27.550+0200 [DEBUG] [TestEventLogger] Pre Scope
2021-09-16T20:05:27.629+0200 [DEBUG] [TestEventLogger] onSuccess start
2021-09-16T20:05:27.630+0200 [DEBUG] [TestEventLogger] onSuccess end
2021-09-16T20:05:27.630+0200 [DEBUG] [TestEventLogger] Post Scope
2021-09-16T20:05:27.630+0200 [DEBUG] [TestEventLogger] loadModel end
我对 Kotlin 协程还很陌生,但是我已经在谷歌上搜索了很多关于这个问题的东西,而且似乎没有其他人有这个问题,所以我只能假设我在这里做了一些非常愚蠢的事情......
【问题讨论】:
标签: android unit-testing kotlin retrofit2 kotlin-coroutines