包含协程延迟时如何对协程进行单元测试?

How to unit test coroutine when it contains coroutine delay?

当我在我的视图模型中添加协程 delay() 时,代码的剩余部分将不会执行。

这是我的演示代码:

class SimpleViewModel : ViewModel(), CoroutineScope {

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Unconfined

    var data = 0

    fun doSomething() {
        launch {
            delay(1000)
            data = 1
        }
    }
}

class ScopedViewModelTest {

    @Test
    fun coroutineDelay() {
        // Arrange
        val viewModel = SimpleViewModel()

        // ActTes
        viewModel.doSomething()

        // Assert
        Assert.assertEquals(1, viewModel.data)
    }
}

我得到断言结果:

java.lang.AssertionError: 
Expected :1
Actual   :0

知道如何解决这个问题吗?

您启动了一个协程,该协程在将 data 设置为 1 之前暂停 1 秒。您的测试仅调用 doSomething 但不会等到 data 实际被设置。如果您添加另一个更长的 delay 到测试中,它将起作用:

@Test     
fun coroutineDelay() = runBlocking {
    ...
    viewModel.doSomething()
    delay(1100)
    ...
}

你也可以让协程return成为你可以等待的Deferred:

fun doSomething(): Deferred<Unit> {
    return async {
        delay(1000)
        data = 1
    }
}

有了 await 就没有必要再延迟你的代码了:

val model = SimpleViewModel()
model.doSomething().await()

您的代码中的第一个问题是 SimpleViewModel.coroutineContext 没有与之关联的 Job。使您的视图模型成为 CoroutineScope 的全部意义在于能够集中取消它启动的所有协程。所以添加作业如下(注意自定义getter的不存在):

class SimpleViewModel : ViewModel(), CoroutineScope {

    override val coroutineContext = Job() + Dispatchers.Unconfined

    var data = 0

    fun doSomething() {
        launch {
            delay(1000)
            data = 1
        }
    }
}

现在,您的测试代码可以确保只有在您的视图模型启动的所有作业都完成后,它才会继续执行断言:

class ScopedViewModelTest {

    @Test
    fun coroutineDelay() {
        // Arrange
        val viewModel = SimpleViewModel()

        // ActTes
        viewModel.doSomething()

        // Assert
        runBlocking {
            viewModel.coroutineContext[Job]!!.children.forEach { it.join() }
        }
        Assert.assertEquals(1, viewModel.data)
    }
}