当使用 Kotlin 协程时,我如何对调用挂起函数的函数进行单元测试?

37

我有一个像这样的类

class SomeClass {
    fun someFun() {
        // ... Some synchronous code
        async {
            suspendfun() 
        }
    }

    private suspend fun suspendFun() {
         dependency.otherFun().await()
         // ... other code
    }
}

我希望对someFun()进行单元测试,因此我编写了一个单元测试,代码如下:

@Test
fun testSomeFun() {
    runBlocking {
        someClass.someFun()
    }

    // ... verifies & asserts
}

但是这似乎行不通,因为runBlocking并不会阻塞执行直到其中所有内容完成。如果我直接在runBlocking内部测试suspendFun()它将按预期工作,但我希望能够同时测试someFun()

有什么线索可以测试同时包含同步和异步代码的函数吗?

1个回答

21

修复异步问题

当前实现的someFun()仅会“执行并忘记”async结果。因此,在该测试中,runBlocking没有任何区别。

如果可能,请使someFun()返回asyncDeferred,然后在runBlocking中调用await

fun someFun(): Deferred<Unit> {
    // ... Some synchronous code
    return async {
        suspendFun()
    }
}

接下来是测试:

runBlocking {
    SomeClass().someFun().await()
}

这篇问答是获取更多信息的好资源。

替代方案:使用 launch

也可以避免使用async,而是使用suspend函数和由launch创建的协程:

suspend fun someFun() {
    // ... Some synchronous code
    suspendFun()
}

private suspend fun suspendFun() {
    delay(1000)
    println("executed")
    // ... other code
}
测试使用 launch,外部的 runBlocking 隐式等待其完成:
val myScope = GlobalScope
runBlocking {
    myScope.launch {
        SomeClass().someFun()
    }
}

这是否意味着如果您不调用.await(),异步块将不会传播任何异常? - user1809913
3
如果您忽略 async 函数的返回值,就不应该使用它。而应该使用 launch 代替,这样未处理的异常会在恢复线程中抛出。 - Marko Topolnik
@MarkoTopolnik 我明白了,我在错误地使用async,而应该使用launch。 - user1809913
3
为什么要使用 runBlocking { launch { ... }.join() }?难道不能只用 runBlocking { ... } 吗? - msrd0
@msrd0,你说得对,虽然这并不总是如此。我已经更新了代码。 - s1m0nw1
使用第二种方法,我们也需要在拆除(teardown)中取消myScope以避免测试出现干扰吗? - user1228891

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接