Единичное тестирование сопрограммы Kotlin с задержкой
Я пытаюсь выполнить тестирование тестовой программы Kotlin, использующей delay()
. Для модульного теста я не забочусь о delay()
, это просто замедляет тест. Я бы хотел каким-то образом запустить тест, который фактически не задерживается при вызове функции delay()
.
Я попытался запустить сопрограмму coroutine, используя специальный контекст, который делегирует CommonPool:
class TestUiContext : CoroutineDispatcher(), Delay {
suspend override fun delay(time: Long, unit: TimeUnit) {
// I'd like it to call this
}
override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
// but instead it calls this
}
override fun dispatch(context: CoroutineContext, block: Runnable) {
CommonPool.dispatch(context, block)
}
}
Я надеялся, что смогу просто вернуться из метода context delay()
, но вместо этого он вызывает мой метод scheduleResumeAfterDelay()
, и я не знаю, как делегировать его планировщику по умолчанию.
Ответы
Ответ 1
В kotlinx.coroutines v1.2.1 они добавили модуль kotlinx-coroutines-test. Он включает в себя конструктор сопрограмм runBlockingTest
, а также TestCoroutineScope
и TestCoroutineDispatcher
. Они позволяют автоматически продвигаться по времени, а также явно контролировать время для тестирования сопрограмм с помощью delay
.
Ответ 2
Если вы не хотите какой-либо задержки, почему бы вам просто не возобновить продолжение в расписании?
class TestUiContext : CoroutineDispatcher(), Delay {
override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
continuation.resume(Unit)
}
override fun dispatch(context: CoroutineContext, block: Runnable) {
//CommonPool.dispatch(context, block) // dispatch on CommonPool
block.run() // dispatch on calling thread
}
}
Таким образом delay()
возобновится без задержки. Обратите внимание, что это все еще приостанавливается при задержке, поэтому другие сопрограммы еще могут выполняться (например, yield()
)
@Test
fun 'test with delay'() {
runBlocking(TestUiContext()) {
launch { println("launched") }
println("start")
delay(5000)
println("stop")
}
}
Работает без задержек и отпечатков:
start
launched
stop
РЕДАКТИРОВАТЬ:
Вы можете контролировать, где выполняется продолжение, настраивая функцию dispatch
.
Ответ 3
В kotlinx.coroutines v0.23.0 они представили TestCoroutineContext.
Pro: это позволяет действительно тестировать сопрограммы с delay
. Вы можете установить виртуальные часы CoroutineContext на определенный момент времени и проверить ожидаемое поведение.
Con: если ваш сопрограммный код не использует delay
, и вы просто хотите, чтобы он выполнялся синхронно в вызывающем потоке, использовать его несколько сложнее, чем TestUiContext
из ответа @bj0 (вам нужно вызвать triggerActions()
на TestCoroutineContext, чтобы получить сопрограмму для выполнения).
Примечание:
TestCoroutineContext
теперь живет в модуле kotlinx-coroutines-test
, начиная с сопрограмм версии 1.2.1, и будет помечен как устаревший или не существует в стандартной библиотеке сопрограмм в версиях выше этой версии.