gpt4 book ai didi

unit-testing - 如何在单元测试中等待suspendCoroutine?

转载 作者:行者123 更新时间:2023-12-04 05:24:47 31 4
gpt4 key购买 nike

我想为我的 android 应用程序编写测试。有时 viewModel 使用 Kotlins 协程启动功能在后台执行任务。这些任务在 androidx.lifecycle 库如此方便地提供的 viewModelScope 中执行。为了仍然测试这些功能,我将默认的 android Dispatchers 替换为 Dispatchers.Unconfined,它会同步运行代码。

至少在大多数情况下。使用 suspendCoroutine 时,Dispatchers.Unconfined 不会被挂起并稍后恢复,而是简单地返回。 Dispatchers.Unconfined 的文档揭示原因:

[Dispatchers.Unconfined] lets the coroutine resume in whatever thread that is used by the corresponding suspending function.



所以据我了解,协程实际上并没有被挂起,而是 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 提供这行不通。如果可能的话,我不希望在任何地方都注入(inject) CoroutineScope,因为任何较低级别的类都可以(并且确实)像示例中那样制作自己的 CoroutineScope,然后测试必须了解很多有关实现细节的信息。作用域背后的想法是每个类都可以控制取消它们的异步操作。例如 viewModelScope 在 vi​​ewModel 被销毁时默认被取消。

我的问题:
我可以使用什么协程调度程序来运行启动和 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

关于unit-testing - 如何在单元测试中等待suspendCoroutine?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56851215/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com