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

问题描述 投票:1回答:2

我正在创建一个库,我正在使用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之外也会使测试失败,应该这样做。

任何输入,帮助,良好实践等表示赞赏!

android unit-testing kotlin kotlinx.coroutines
2个回答
2
投票

您可以为doIt()函数明确提供CoroutineContext:

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

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

runBlocking {
    doer.doIt(coroutineContext)
}

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

1
投票

最好的方法是不要像你现在那样在你的Job函数中吞下doIt()。 代替

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

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

这样你的函数将返回一个coroutine,你可以等待:

doIt().join()

更好的是使用async()而不是launch()

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

© www.soinside.com 2019 - 2024. All rights reserved.