首页 > 解决方案 > 如何在函数中测试 Kotlin 协程?

问题描述

我正在创建一个库,并且我正在使用 Retrofit 和一个调用适配器,它给了我一个 Deferred<> 值。

在我调用的代码中的一个函数中,在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"
        }
    }
}

我想要的是实际运行所有功能。测试至少需要 5 秒,但它只在几毫秒内运行完代码。它不会阻塞。

我试过添加

runBlocking {
    launch {
        // doer.doIt()
    }.joinChildren()
}

和类似的做法,但我只是无法让测试在测试完成之前真正等待我在另一个课程中的启动完成。放置verify(...)外部runBlocking也会使测试失败,这是应该的。

任何输入、帮助者、良好实践等都将受到赞赏!

标签: androidunit-testingkotlinkotlinx.coroutines

解决方案


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

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

使用此参数,您可以轻松更改协程上下文 - 在您的测试代码中,您使用阻塞上下文:

runBlocking {
    doer.doIt(coroutineContext)
}

顺便说一句:您不需要使用launchand 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"
}

推荐阅读