Как проверить сопрограммы 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.