Единичное тестирование сопрограммы 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, и будет помечен как устаревший или не существует в стандартной библиотеке сопрограмм в версиях выше этой версии.