Как проверить сопрограммы 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 также делает тест неудачным, что и должно быть.

Любой вклад, помощники, хорошая практика и т. Д. Приветствуется!

2 ответа

Вы можете предоставить CoroutineContext явно для вашего doIt() функция:

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

С помощью этого параметра вы можете легко изменить контекст сопрограммы - в своем тестовом коде вы используете блокирующий контекст:

runBlocking {
    doer.doIt(coroutineContext)
}

Кстати: вам не нужно использовать launch а также async, С launch ты в suspendable контекст, и вам не нужно бежать theThing() асинхронно. Особенно если вы вызываете àwait() на следующем шаге:

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

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

Лучший способ - не глотать Job в вашем doIt() функционировать, как вы делаете сейчас.
Вместо

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

Делать

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

Таким образом, ваша функция вернет сопрограмму, которую вы можете ждать:

doIt().join()

Еще лучше использовать async() вместо launch()

Еще один комментарий заключается в том, что doIt() должно быть на самом деле doItAsync()В соответствии с рекомендациями Kotlin.

Другие вопросы по тегам