Interoperabilidad

Compose se integra con frameworks de prueba comunes.

Interoperabilidad con Espresso

En una app híbrida, puedes encontrar componentes de Compose dentro de jerarquías de vistas y vistas en elementos componibles de Compose (a través del elemento componible AndroidView).

No es necesario realizar pasos especiales para coincidir con alguno de los tipos. Haces coincidir las vistas con onView de Espresso y los elementos de Compose con ComposeTestRule.

@Test
fun androidViewInteropTest() {
    // Check the initial state of a TextView that depends on a Compose state.
    Espresso.onView(withText("Hello Views")).check(matches(isDisplayed()))
    // Click on the Compose button that changes the state.
    composeTestRule.onNodeWithText("Click here").performClick()
    // Check the new value.
    Espresso.onView(withText("Hello Compose")).check(matches(isDisplayed()))
}

Se agregó semántica con alcance de View para pruebas de interoperabilidad de Compose

Cómo limitar las búsquedas de Compose a Views específicas

Cuando migres IU complejas a Compose, es posible que encuentres elementos de Compose idénticos anidados dentro de varias vistas tradicionales de Android, como dentro de un RecyclerView o un ViewPager. En estos casos, una búsqueda estándar de Compose, como onNodeWithText("Save"), podría fallar con un error de "Se encontraron varios nodos".

En lugar de modificar tu código de producción para insertar etiquetas de prueba dinámicas y distinguir estos elementos, puedes definir el alcance de tu prueba de Compose directamente en una View de Android específica.

Usa la API de onRootWithViewInteraction en tu regla de prueba. Esta función acepta un ViewInteraction de Espresso, lo que te permite aprovechar Espresso para aislar una View de contenedor específica y realizar interacciones de Compose exclusivamente dentro de esa jerarquía con alcance.

Interactúa con un elemento de la lista

Si necesitas interactuar con un elemento de Compose dentro de una fila RecyclerView específica, usa Espresso para ubicar la fila y, luego, limita tu interacción de Compose a ella. Esto ignora los elementos de Compose idénticos en todas las demás filas.

@Test
fun testComposeButtonInsideRecyclerViewItem() = runComposeUiTest {
    // Scroll to the desired position using Espresso
    Espresso.onView(withId(recyclerViewId))
        .perform(RecyclerViewActions.scrollToPosition<MyViewHolder>(3))

    // Define an Espresso ViewInteraction that uniquely identifies the row
    val rowView = Espresso.onView(
        allOf(
            withId(rootViewId),
            hasDescendant(withText("Item #3"))
        )
    )

    // Scope the Compose search strictly to that specific row View
    onRootWithViewInteraction(rowView)
        .onNode(hasText("Like"))
        .performClick()
}

Cómo resolver la ambigüedad en ViewPagers

Cuando hay varios fragmentos con diseños de Compose idénticos en la memoria de forma simultánea, puedes definir el alcance de la búsqueda para el ID de View raíz del fragmento específico y, así, evitar la ambigüedad en la coincidencia.

@Test
fun testComposeButtonInsideViewPagerItem() = runComposeUiTest {
    // Swipe to the desired page using Espresso
    Espresso.onView(withId(viewPagerViewId)).perform(swipeLeft())

    // Identify the specific container view using Espresso
    val fragmentB = Espresso.onView(withId(fragmentRootViewId))

    // The generic text "Save" is now unique within this view scope
    onRootWithViewInteraction(fragmentB)
        .onNode(hasText("Save"))
        .assertIsDisplayed()
}

Interoperabilidad con UiAutomator

De forma predeterminada, solo se puede acceder a los elementos componibles desde UiAutomator a través de sus descriptores convenientes (texto mostrado, descripción del contenido, etcétera). Si quieres acceder a cualquier elemento componible que use Modifier.testTag, debes habilitar la propiedad semántica testTagsAsResourceId para el subárbol del elemento componible en particular. Habilitar este comportamiento es útil para elementos componibles que no tienen ningún otro controlador único, como los que se pueden desplazar (por ejemplo, LazyColumn).

Habilita la propiedad semántica solo una vez en la parte superior de la jerarquía de elementos componibles para asegurarte de que se pueda acceder a todos los elementos componibles anidados con Modifier.testTag desde UiAutomator.

Scaffold(
    // Enables for all composables in the hierarchy.
    modifier = Modifier.semantics {
        testTagsAsResourceId = true
    }
){
    // Modifier.testTag is accessible from UiAutomator for composables nested here.
    LazyColumn(
        modifier = Modifier.testTag("myLazyColumn")
    ){
        // Content
    }
}

Se puede acceder a cualquier elemento componible con Modifier.testTag(tag) mediante el uso de By.res(resourceName) con el mismo tag que resourceName.

val device = UiDevice.getInstance(getInstrumentation())

val lazyColumn: UiObject2 = device.findObject(By.res("myLazyColumn"))
// Some interaction with the lazyColumn.

Recursos adicionales

  • Cómo probar apps en Android: La página de destino principal de las pruebas de Android proporciona una vista más amplia de los aspectos básicos y las técnicas de las pruebas.
  • Fundamentos de las pruebas: Obtén más información sobre los conceptos básicos de las pruebas de una app para Android.
  • Pruebas locales: Puedes ejecutar algunas pruebas de forma local en tu propia estación de trabajo.
  • Pruebas instrumentadas: También es una buena práctica ejecutar pruebas instrumentadas. Es decir, pruebas que se ejecutan directamente integrado en el dispositivo.
  • Integración continua: La integración continua te permite integrar tus pruebas en tu canalización de implementación.
  • Prueba diferentes tamaños de pantalla: Con tantos dispositivos disponibles para los usuarios, debes probar diferentes tamaños de pantalla.
  • Espresso: Si bien está diseñado para las IU basadas en View, el conocimiento de Espresso puede ser útil para algunos aspectos de las pruebas de Compose.