Testing Rules para Coroutines
Clase 9 de 16 • Curso de Android Testing
Resumen
La programación de pruebas en Kotlin con corrutinas puede ser un desafío, especialmente cuando necesitamos configurar y restablecer el dispatcher principal en cada test. Automatizar este proceso no solo ahorra tiempo, sino que también reduce la posibilidad de errores. En este artículo, exploraremos cómo crear una solución elegante para manejar los dispatchers en nuestras pruebas de corrutinas, mejorando así la calidad y mantenibilidad de nuestro código.
¿Por qué necesitamos una solución más elegante para nuestros tests con corrutinas?
Hasta ahora, cada vez que testeamos un ViewModel con corrutinas, hemos tenido que escribir manualmente Dispatchers.setMain()
en el método before
y Dispatchers.resetMain()
en el método after
. Este enfoque funciona, pero es repetitivo y propenso a errores si olvidamos incluir estas líneas en algún test.
La repetición de código no solo hace que nuestros tests sean más largos, sino que también dificulta el mantenimiento. Si necesitamos cambiar la forma en que configuramos los dispatchers, tendríamos que modificar cada test individualmente, lo que podría llevar a inconsistencias.
¿Cómo podemos encapsular la lógica de dispatchers para tests?
La solución es crear una clase reutilizable que encapsule esta lógica común. Para esto, utilizaremos TestWatcher
, una clase proporcionada por JUnit que nos permite ejecutar código antes y después de cada test.
@OptIn(ExperimentalCoroutinesApi::class)
class MainDispatcherRule(
private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
) : TestWatcher() {
override fun starting(description: Description) {
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description) {
Dispatchers.resetMain()
}
}
Esta clase MainDispatcherRule
extiende de TestWatcher
y nos permite acceder a dos momentos clave:
starting
: Se ejecuta al inicio de cada testfinished
: Se ejecuta al final de cada test
La clase recibe un TestDispatcher
como parámetro, con un valor predeterminado de UnconfinedTestDispatcher
. Esto nos da flexibilidad para usar diferentes tipos de dispatchers según las necesidades de nuestros tests.
¿Cómo implementar nuestra regla de dispatcher en los tests existentes?
Una vez creada nuestra clase MainDispatcherRule
, podemos implementarla en nuestros tests de ViewModel de la siguiente manera:
class ViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
// Nuestros tests aquí
}
Con esta simple anotación @get:Rule
, ya no necesitamos escribir manualmente Dispatchers.setMain()
y Dispatchers.resetMain()
en cada test. La regla se encargará automáticamente de configurar y restablecer el dispatcher principal.
¿Qué ocurre con diferentes tipos de dispatchers?
Es importante tener en cuenta que diferentes tipos de dispatchers pueden requerir configuraciones adicionales. Por ejemplo, si usamos StandardTestDispatcher
en lugar de UnconfinedTestDispatcher
, necesitaremos incluir advanceUntilIdle()
en nuestros tests:
class ViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule(StandardTestDispatcher())
@Test
fun someTest() {
// Configuración del test
// Acción a probar
// Avanzar hasta que todas las corrutinas estén inactivas
testScheduler.advanceUntilIdle()
// Verificaciones
}
}
Si no incluimos advanceUntilIdle()
cuando usamos StandardTestDispatcher
, nuestros tests podrían fallar porque las corrutinas no tendrían tiempo de completarse antes de las verificaciones.
¿Cuáles son los beneficios de este enfoque?
La implementación de MainDispatcherRule
nos proporciona varios beneficios:
- Código más limpio y conciso: Eliminamos la repetición de código en nuestros tests.
- Mayor mantenibilidad: Si necesitamos cambiar la forma en que configuramos los dispatchers, solo tenemos que modificar una clase.
- Menos propenso a errores: No hay riesgo de olvidar configurar o restablecer el dispatcher principal.
- Flexibilidad: Podemos usar diferentes tipos de dispatchers según las necesidades de cada test.
Este patrón de diseño sigue el principio DRY (Don't Repeat Yourself), lo que hace que nuestro código de prueba sea más profesional y fácil de mantener a largo plazo.
La creación de clases de utilidad como MainDispatcherRule
es una práctica recomendada en el desarrollo de software que nos ayuda a escribir código más limpio y mantenible. En futuros artículos, exploraremos cómo probar flows de forma estructurada y sencilla usando Turbine, una librería diseñada específicamente para testear flows de manera declarativa. ¿Has implementado soluciones similares en tus proyectos? Comparte tu experiencia en los comentarios.