Testing de funciones suspendidas con runTest y runBlocking
Clase 7 de 16 • Curso de Android Testing
Resumen
Las aplicaciones modernas a menudo incluyen operaciones asíncronas como peticiones de red, acceso a bases de datos o llamadas a servicios remotos. En Kotlin, estas operaciones suelen realizarse mediante funciones suspendidas (suspend functions) y coroutinas. Esto implica nuevos desafíos al hacer pruebas unitarias, ya que necesitamos herramientas especiales como runBlocking y runTest de la librería Kotlin Coroutine Test para asegurar la fiabilidad y rapidez en test unitarios.
¿Qué son las funciones suspendidas en Kotlin?
Las funciones suspendidas o suspend functions permiten pausar y reanudar tareas, facilitando el trabajo en operaciones con retardo como redes o bases de datos. En nuestros ejemplos, creamos una función delayOperation
:
suspend fun delayOperation(): Int {
delay(1000L)
return 42
}
Aquí, la palabra clave delay
causa una suspensión temporal en la ejecución.
¿Cómo funciona runBlocking vs. runTest en los tests?
Para manejar estas coroutinas durante las pruebas unitarias, dos funciones comunes son:
- runBlocking: permite llamar funciones suspendidas pero ejecuta realmente los retrasos establecidos en la operación.
- runTest: diseñada especialmente para probar coroutinas, simula tiempo y evita retrasos reales, acelerando significativamente las pruebas.
En nuestro test con runBlocking
comprobamos que, aunque pasa la prueba, tarda más de un segundo debido al delay
. En cambio, usando runTest
, las demoras se simulan y el test se completa casi inmediatamente, haciendo que las pruebas unitarias sean más eficientes.
¿Cómo testear flujos (flows) utilizando runTest?
Al implementar flujos (flows), que son secuencias de valores emitidos con pausas, podemos necesitar métodos específicos de prueba. Observa este ejemplo de flow:
val numberFlow = flow {
emit(1)
delay(1000)
emit(2)
delay(1000)
emit(3)
}
Al integrar esta estructura en pruebas, runTest
permite hacer simulaciones rápidas:
- Al usar
runBlocking
, cadadelay
es real, aumentando considerablemente el tiempo de ejecución. - Con
runTest
, esos mismos retrasos se simulan logrando ejecutar pruebas más rápidas.
Es posible incluso controlar artificialmente el paso del tiempo en un test, utilizando herramientas provistas en runTest
como advanceTimeBy
. De esta manera probamos que nuestras emisiones ocurran en tiempos específicos:
val numbers = mutableListOf<Int>()
val job = launch { numberFlow.collect { numbers.add(it) } }
advanceTimeBy(500)
// Hasta aquí solo debe emitirse el "1"
advanceTimeBy(600)
// Ahora debe haberse emitido también "2"
Este control brinda gran precisión y eficiencia en validaciones unitarias de código reactivo asincrónico.
Ahora que tienes más claro cómo utilizar runBlocking
y runTest
, ¿cuál prefieres para mejorar el desempeño en tus pruebas unitarias? ¡Comparte tu experiencia!