unit-testing - 如何在单元测试中等待suspendCoroutine?
问题描述
我想为我的 android 应用程序编写测试。有时 viewModel 使用 Kotlins 协程启动功能在后台执行任务。这些任务在 androidx.lifecycle 库如此方便地提供的 viewModelScope 中执行。为了仍然测试这些功能,我将默认的 android Dispatchers 替换为 Dispatchers.Unconfined,它会同步运行代码。
至少在大多数情况下。使用 suspendCoroutine 时,Dispatchers.Unconfined 不会被挂起并稍后恢复,而是简单地返回。的文档Dispatchers.Unconfined
揭示了原因:
[Dispatchers.Unconfined] 让协程在相应挂起函数使用的任何线程中恢复。
所以据我了解,协程实际上并没有挂起,而是在suspendCoroutine
调用continuation.resume
. 因此测试失败。
例子:
class TestClassTest {
var testInstance = TestClass()
@Test
fun `Test with default dispatchers should fail`() {
testInstance.runAsync()
assertFalse(testInstance.valueToModify)
}
@Test
fun `Test with dispatchers replaced by Unconfined should pass`() {
testInstance.DefaultDispatcher = Dispatchers.Unconfined
testInstance.IODispatcher = Dispatchers.Unconfined
testInstance.runAsync()
assertTrue(testInstance.valueToModify)
}
@Test
fun `I need to also test some functions that use suspend coroutine - How can I do that?`() {
testInstance.DefaultDispatcher = Dispatchers.Unconfined
testInstance.IODispatcher = Dispatchers.Unconfined
testInstance.runSuspendCoroutine()
assertTrue(testInstance.valueToModify)//fails
}
}
class TestClass {
var DefaultDispatcher: CoroutineContext = Dispatchers.Default
var IODispatcher: CoroutineContext = Dispatchers.IO
val viewModelScope = CoroutineScope(DefaultDispatcher)
var valueToModify = false
fun runAsync() {
viewModelScope.launch(DefaultDispatcher) {
valueToModify = withContext(IODispatcher) {
sleep(1000)
true
}
}
}
fun runSuspendCoroutine() {
viewModelScope.launch(DefaultDispatcher) {
valueToModify = suspendCoroutine {
Thread {
sleep(1000)
//long running operation calls listener from background thread
it.resume(true)
}.start()
}
}
}
}
我正在尝试runBlocking
使用CoroutineScope
. runBlocking
但是由于我的代码是在CoroutineScope
提供的viewModelScope
this 上启动的,所以它不起作用。如果可能的话,我不希望在任何地方都注入 CoroutineScope,因为任何较低级别的类都可以(并且确实)像示例中那样制作自己的 CoroutineScope,然后测试必须了解很多有关实现细节的信息。作用域背后的想法是每个类都可以控制取消它们的异步操作。例如 viewModelScope 在 viewModel 被销毁时默认被取消。
我的问题:
我可以使用什么协程调度程序来运行启动和 withContext 等阻塞(如Dispatchers.Unconfined
)并且还运行 suspendCoroutine 阻塞?
解决方案
将协程上下文传递给您的模型怎么样?就像是
class Model(parentContext: CoroutineContext = Dispatchers.Default) {
private val modelScope = CoroutineScope(parentContext)
var result = false
fun doWork() {
modelScope.launch {
Thread.sleep(3000)
result = true
}
}
}
@Test
fun test() {
val model = runBlocking {
val model = Model(coroutineContext)
model.doWork()
model
}
println(model.result)
}
从 androidx.lifecycle更新 viewModelScope 你可以使用这个测试规则
@ExperimentalCoroutinesApi
class CoroutinesTestRule : TestWatcher() {
private val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
dispatcher.cleanupTestCoroutines()
}
}
这是测试模型和测试
class MainViewModel : ViewModel() {
var result = false
fun doWork() {
viewModelScope.launch {
Thread.sleep(3000)
result = true
}
}
}
class MainViewModelTest {
@ExperimentalCoroutinesApi
@get:Rule
var coroutinesRule = CoroutinesTestRule()
private val model = MainViewModel()
@Test
fun `check result`() {
model.doWork()
assertTrue(model.result)
}
}
不要忘记添加testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
以获取TestCoroutineDispatcher
推荐阅读
- python - 当 ORTOOLS 提出解决方案时评估自定义函数
- python - 在 4 GB RAM 笔记本电脑上制作大型机器学习模型
- ios - 注册流程到错误的视图控制器并且也不写入firestore
- reactjs - React 数据内容在回调时消失
- spring-cloud-stream - Cloud Foundry 上的 StreamBridge RabbitMQ 连接被拒绝
- android - Jetpack Compose 道具打字
- ansible - 如何将运行 ansible playbook 的用户添加到组中
- sql - 加入 sql 查询以获取附加列
- java - Oracle Apex、Apache PDFBox java 存储过程问题
- terraform - 在 Terraform 和 Cloudbuild 中找不到 Gsutil