Testing de Navegación
Clase 13 de 16 • Curso de Android Testing
Resumen
La prueba de flujos de navegación en Jetpack Compose representa un paso fundamental en el desarrollo de aplicaciones Android robustas. Más allá de verificar componentes individuales, estas pruebas simulan cómo los usuarios interactúan con múltiples pantallas, garantizando una experiencia fluida y predecible. Dominar estas técnicas te permitirá construir aplicaciones más confiables y detectar problemas de navegación antes de que lleguen a producción.
¿Cómo configurar un test de navegación en Jetpack Compose?
Para comenzar a testear flujos de navegación en Jetpack Compose, necesitamos configurar correctamente nuestro entorno de pruebas. Esto implica crear un controlador de navegación específico para testing y preparar los datos necesarios para simular un flujo real.
En nuestra carpeta de Android test, crearemos un nuevo archivo para nuestro test de navegación:
class NavigationTest {
@get:Rule
val composeRule = createComposeRule()
private lateinit var navController: TestNavHostController
private val testPlace = Place(
id = 1,
name = "test place",
coordinates = Coordinates(1.0, 1.0)
)
private lateinit var testProfile: Profile
@Before
fun setup() {
testProfile = Profile(
user = User(id = "test user", name = "TestUser"),
places = listOf(testPlace)
)
}
}
En esta configuración inicial:
- Utilizamos
createComposeRule()
para poder interactuar con nuestros componentes Compose - Declaramos un
TestNavHostController
que nos permitirá controlar y verificar la navegación - Creamos datos de prueba (un lugar y un perfil) que utilizaremos durante el test
Inicialización del controlador de navegación para testing
A diferencia de la navegación en código de producción, para testing necesitamos configurar el controlador de navegación de una manera específica:
@Test
fun navigationStateIsCorrectWhenNavigatingToDetailScreen() {
composeRule.setContent {
navController = TestNavHostController(LocalContext.current)
navController.navigatorProvider.addNavigator(ComposeNavigator())
NavHost(
navController = navController,
startDestination = "profile"
) {
composable("profile") {
ProfileScreen(
state = testProfile,
onPlaceClick = { place ->
navController.navigate("detail/${place.id}")
}
)
}
composable("detail/{placeId}") { backStackEntry ->
val placeId = backStackEntry.arguments?.getString("placeId")
val place = testProfile.places.find { it.id.toString() == placeId }
DetailPlaceScreen(
place = place,
onNavigateBack = {
navController.popBackStack()
}
)
}
}
}
}
En este fragmento:
- Inicializamos el
TestNavHostController
con el contexto actual - Configuramos el
NavHost
con las rutas que queremos probar - Definimos los composables para cada ruta, incluyendo las acciones de navegación
¿Cómo simular interacciones de usuario y verificar la navegación?
Una vez configurado nuestro entorno, podemos simular las interacciones del usuario y verificar que la navegación funciona correctamente:
// Simulamos clic en la tarjeta del lugar
composeRule.onNodeWithContentDescription("place card test place")
.performClick()
// Esperamos a que se complete la navegación
composeRule.waitForIdle()
// Verificamos que estamos en la pantalla de detalle
assertTrue(navController.currentDestination?.route?.startsWith("detail") == true)
// Simulamos clic en el botón de regreso
composeRule.onNodeWithContentDescription("navigate back")
.performClick()
// Esperamos a que se complete la navegación
composeRule.waitForIdle()
// Verificamos que hemos vuelto a la pantalla de perfil
assertTrue(navController.currentDestination?.route == "profile")
Es importante destacar el uso de waitForIdle()
después de cada interacción. Esto permite que todas las operaciones pendientes en el hilo principal se completen antes de realizar nuestras verificaciones, evitando condiciones de carrera y resultados inconsistentes entre diferentes dispositivos.
Manejo de transiciones y sincronización
Cuando trabajamos con navegación en tests, debemos tener en cuenta que las transiciones entre pantallas no son instantáneas. Por ello, es crucial sincronizar correctamente nuestras acciones y verificaciones:
// Simulamos clic en la tarjeta
composeRule.onNodeWithContentDescription("place card test place")
.performClick()
// Esperamos a que se complete la navegación
composeRule.waitForIdle()
Thread.sleep(1000) // Opcional: para visualizar mejor la transición durante el test
// Verificación después de que la navegación se ha completado
assertTrue(navController.currentDestination?.route?.startsWith("detail") == true)
El uso de Thread.sleep()
es opcional y principalmente útil durante el desarrollo para visualizar las transiciones. En tests automatizados, waitForIdle()
suele ser suficiente para garantizar la sincronización.
¿Qué ventajas ofrece el testing de navegación en Compose?
El testing de navegación en Jetpack Compose proporciona múltiples beneficios para el desarrollo de aplicaciones Android:
- Validación de flujos completos: Permite verificar que los usuarios pueden completar tareas que involucran múltiples pantallas.
- Detección temprana de problemas: Identifica errores en la configuración de rutas, argumentos o transiciones antes de llegar a producción.
- Regresiones controladas: Evita que cambios en el código rompan flujos de navegación existentes.
- Documentación viva: Los tests actúan como documentación ejecutable de cómo se espera que funcione la navegación en la aplicación.
El enfoque de testing que hemos visto simula con precisión cómo los usuarios reales interactúan con la aplicación, desde hacer clic en elementos hasta navegar entre pantallas y regresar a pantallas anteriores.
La capacidad de testear estos flujos de manera automatizada mejora significativamente la calidad y confiabilidad de nuestras aplicaciones Android, especialmente cuando la complejidad de la navegación aumenta con el tiempo.
Los conceptos aprendidos en esta clase sentarán las bases para pruebas más avanzadas que incluirán inyección de dependencias con Hilt y testing de flujos completos de aplicaciones, temas que se abordarán en próximas sesiones.
¿Has implementado tests de navegación en tus proyectos? ¿Qué desafíos has encontrado al probar flujos complejos? Comparte tu experiencia en los comentarios.