Pruebas UI en Jetpack Compose con createComposeRule

Resumen

Testear interfaces en Jetpack Compose te permite validar que cada pantalla responde como esperas frente a estados de carga, datos y error. Con createComposeRule y los finders semánticos, puedes montar composables aislados y verificar su comportamiento sin levantar toda la app. Es un recurso clave si trabajas con Android moderno y quieres asegurar calidad visual y accesibilidad.

Por qué usar createComposeRule en pruebas de Compose

La regla createComposeRule crea una actividad aislada con un propósito de prueba y te da acceso al método setContent, el mismo que usas para cargar una vista en Compose. Esto significa que puedes renderizar un composable concreto, inyectarle un estado y verificar su comportamiento sin depender de un ViewModel real ni de la navegación completa de la app.

En la pantalla de ejemplo, Profile Screen, hay tres elementos que vuelven el test confiable:

  • Un contentDescription como loading profile para el indicador de carga.
  • Textos visibles como el welcome con el nombre del usuario.
  • Place cards con descripciones semánticas que incluyen el nombre o el ID del lugar.

Estos atributos cumplen una doble función: mejoran la accesibilidad y, al mismo tiempo, te permiten identificar las vistas con precisión durante el test [00:42].

¿Qué hace createComposeRule? Crea una actividad aislada de pruebas y expone setContent, donde montas el composable que quieres validar. Es la base de cualquier test de UI en Compose.

Cómo escribir el primer test de Profile Screen paso a paso

Los tests de UI no van en la carpeta de unit tests, sino en androidTest, porque participan elementos concretos del framework de Android como Compose [02:15]. Dentro de presentation, creas un archivo ProfileScreenTest y declaras la regla:

kotlin @get:Rule val composeRule = createComposeRule()

Cómo preparar el estado y montar el composable

Para simular datos reales, defines una función previewProfileState que devuelve un ProfileState con un testUser y un par de places con IDs 1.1 y 2.2. Luego, dentro del test, llamas a composeRule.setContent { } y montas la pantalla con tu tema y el estado preparado.

La estructura sigue el patrón given–when–then: el estado es el dato (given), setContent carga la UI (when) y las aserciones validan el resultado (then).

Cómo usar finders y matchers para validar la UI

En Compose, el árbol de composables se representa como nodos. Los finders localizan un nodo y los matchers verifican una condición sobre él. El test inicial valida que el saludo aparece en pantalla:

kotlin composeRule.onNodeWithText("Welcome testUser!").assertIsDisplayed()

De la misma forma, puedes verificar que se muestran los lugares cargados consultando textos como place 1, latitud 1.0 o longitud 1.0. Si un elemento se identifica por su descripción semántica en lugar de por texto, la opción recomendada es onNodeWithContentDescription, especialmente útil para íconos o indicadores de carga [07:30].

Cómo probar los estados de loading y error en Compose

Una pantalla bien testeada cubre al menos tres escenarios: datos cargados, loading y error. Cada uno se prueba inyectando un ProfileState distinto.

Cómo verificar el estado de loading con contentDescription

Para el escenario de carga, montas la pantalla con isLoading = true y validas el indicador usando su descripción semántica:

kotlin composeRule.onNodeWithContentDescription("loading profile").assertIsDisplayed()

Si quieres ver el test ejecutándose en el emulador, puedes dormir el hilo unos segundos con Thread.sleep, aunque no es recomendable en producción. Las alternativas correctas son waitForIdle, que espera a que no haya operaciones pendientes, o waitUntil, que espera por una condición específica [09:50].

¿Cuál es la diferencia entre onNodeWithText y onNodeWithContentDescription? El primero localiza nodos por su texto visible; el segundo, por la descripción semántica usada en accesibilidad. Ambos son finders válidos según cómo identifiques el elemento.

Cómo testear el estado de error en Profile Screen

Para el caso de error, defines un mensaje, lo asocias al errorMessage del estado y verificas que aparece en pantalla con onNodeWithText(...).assertIsDisplayed(). El test queda algo así:

kotlin fun profileScreen_whenError_showErrorMessage()

Declaras la cadena de error como entrada, la pasas dentro del ProfileState y validas su presencia. Con esto cubres los tres flujos críticos de la pantalla.

Buenas prácticas para tests de UI legibles y confiables

Mantener tests claros y mantenibles depende de pequeños hábitos que marcan la diferencia con el tiempo:

  • Usa contentDescription significativos en composables clave para reforzar accesibilidad y testabilidad.
  • Estructura cada test con given–when–then aunque omitas el given cuando el estado es trivial.
  • Prefiere waitForIdle o waitUntil sobre Thread.sleep para sincronizar el test.
  • Cubre al menos los estados de datos, loading y error en cada pantalla.

¿Dónde van los tests de Compose en un proyecto Android? En la carpeta androidTest, no en test, porque dependen del framework de Android y necesitan un dispositivo o emulador para ejecutarse.

Con estos fundamentos cubres los tests de UI básicos en Compose. ¿Qué otro estado de tu pantalla te gustaría validar primero? Cuéntame en los comentarios.