Android 如何在函数内测试Kotlin协程?

Android 如何在函数内测试Kotlin协程?,android,unit-testing,kotlin,kotlinx.coroutines,Android,Unit Testing,Kotlin,Kotlinx.coroutines,我正在创建一个库,并且正在使用一个调用适配器进行改装,该适配器将为我提供一个延迟值 在代码中的一个函数中,我调用launch{},在该函数中,我调用try catch值和可能的异常-为不同的结果调用不同的回调 我在测试协同程序上找到的资源都是关于测试挂起的函数的,runBlocking{}是解决所有问题的方法。除了我,它不是 我举了一个简单的例子 @Mock val mockListener: DoSomething.Listener = mock() @Test fun testSo

我正在创建一个库,并且正在使用一个调用适配器进行改装,该适配器将为我提供一个延迟值

在代码中的一个函数中,我调用
launch{}
,在该函数中,我调用
try catch
值和可能的异常-为不同的结果调用不同的回调

我在测试协同程序上找到的资源都是关于测试挂起的函数的,
runBlocking{}
是解决所有问题的方法。除了我,它不是

我举了一个简单的例子

    @Mock
val mockListener: DoSomething.Listener = mock()

@Test
fun testSomething() {
    val doer = DoSomething(mockListener)
    runBlocking {
        doer.doIt()
        verify(mockListener).listen(any())
    }
}

class DoSomething(val listener: Listener) {

    interface Listener {
        fun listen(s: String)
    }

    fun doIt() {
        launch {
            listener.listen(theThing().await())
        }
    }

    private fun theThing(): Deferred<String> {
        return async {
            delay(5, TimeUnit.SECONDS)
            return@async "Wow, a thing"
        }
    }
}
和类似的实践,但我不能让测试在测试完成之前真正等待我在另一个类中的启动完成。 将
verify(…)
放在
runBlocking
之外也会导致测试失败,这应该是测试失败的原因


感谢您的任何意见、帮助、良好实践等

最好的方法是不要像现在这样吞下
doIt()中的
作业。
而不是

fun doIt() {
    launch {
        listener.listen(theThing().await())
    }
}

这样,您的函数将返回一个协程,您可以等待:

doIt().join()
更好的方法是使用
async()
而不是
launch()


另一个评论是,正如Kotlin指南所建议的那样,
doIt()
实际上应该是
doItAsync()

您可以为
doIt()函数显式提供CoroutineContext:

fun doIt(context: CoroutineContext = DefaultDispatcher) {
    launch(context) {
        listener.listen(theThing().await()
    }
}
使用此参数,您可以轻松更改协同程序上下文-在测试代码中使用阻塞上下文:

runBlocking {
    doer.doIt(coroutineContext)
}
顺便说一句:您不需要使用
launch
async
。使用
launch
时,您处于
suspendable
上下文中,不需要异步运行
theThing()
。尤其是在下一步中调用
await()
时:

fun doIt(context: CoroutineContext = DefaultDispatcher) {
    launch(context) {
        listener.listen(theThing())
    }
}

private suspend fun theThing(): String {
    delay(5, TimeUnit.SECONDS)
    return "Wow, a thing"
}
谢谢你!当然,我不应该在启动中包装该功能,我应该使该功能挂起并启动该功能。这就是你让它可测试的方法。(我不同意发送
coroutineContext
,这似乎有点奇怪。但是
suspend
绝对是实现可测试性的方法!
runBlocking {
    doer.doIt(coroutineContext)
}
fun doIt(context: CoroutineContext = DefaultDispatcher) {
    launch(context) {
        listener.listen(theThing())
    }
}

private suspend fun theThing(): String {
    delay(5, TimeUnit.SECONDS)
    return "Wow, a thing"
}