Listas de Espresso

Espresso ofrece mecanismos para desplazarse o actuar sobre un elemento en particular para dos tipos de listas: vistas de adaptadores y vistas de reciclador.

Cuando trabajes con listas, especialmente las creadas con un RecyclerView o un AdapterView, es posible que la vista que te interesa ni siquiera esté en la pantalla porque solo se muestra un pequeño número de hijos y son reciclados a medida que te desplazas. No se puede usar el método scrollTo() en este caso. porque requiere una vista existente.

Cómo interactuar con elementos de lista de la vista de adaptador

En lugar de usar el método onView(), comienza tu búsqueda con onData() y proporcionar un comparador con los datos que respaldan la vista que deseas. Espresso encontrará la fila en el objeto Adapter y para que el elemento sea visible en el viewport.

Cómo hacer coincidir los datos mediante un buscador de coincidencias de vistas personalizado

La siguiente actividad contiene un ListView, respaldado por un SimpleAdapter. que contiene datos para cada fila en un objeto Map<String, Object>.

La actividad de la lista que se muestra actualmente en la pantalla contiene una lista con
          23 elementos. Cada elemento tiene un número, almacenado como una cadena, asignado a un
          diferente, que se almacena como un Objeto.

Cada mapa tiene dos entradas: una clave "STR" que contiene una cadena, como "item: x" y un "LEN" clave que contiene un Integer, que representa la la duración del contenido. Por ejemplo:

{"STR" : "item: 0", "LEN": 7}

Este es el código para hacer clic en la fila con "item: 50":

Kotlin

onData(allOf(`is`(instanceOf(Map::class.java)), hasEntry(equalTo("STR"),
        `is`("item: 50")))).perform(click())

Java

onData(allOf(is(instanceOf(Map.class)), hasEntry(equalTo("STR"), is("item: 50"))))
    .perform(click());

Ten en cuenta que Espresso se desplaza por la lista automáticamente según sea necesario.

Desmontemos el Matcher<Object> dentro de onData(). El El método is(instanceOf(Map.class)) reduce la búsqueda a cualquier elemento de la AdapterView, que está respaldado por un objeto Map

En nuestro caso, este aspecto de la consulta coincide con cada fila de la vista de lista, pero hacer clic específicamente en un elemento, por lo que delimitamos aún más la búsqueda con lo siguiente:

Kotlin

hasEntry(equalTo("STR"), `is`("item: 50"))

Java

hasEntry(equalTo("STR"), is("item: 50"))

Este Matcher<String, Object> coincidirá con cualquier mapa que contenga una entrada con la clave "STR" y el valor "item: 50". Debido a que el código para buscar esto es por mucho tiempo y queremos reutilizarlo en otras ubicaciones, escribamos una Comparador de withItemContent para eso:

Kotlin

return object : BoundedMatcher<Object, Map>(Map::class.java) {
    override fun matchesSafely(map: Map): Boolean {
        return hasEntry(equalTo("STR"), itemTextMatcher).matches(map)
    }

    override fun describeTo(description: Description) {
        description.appendText("with item content: ")
        itemTextMatcher.describeTo(description)
    }
}

Java

return new BoundedMatcher<Object, Map>(Map.class) {
    @Override
    public boolean matchesSafely(Map map) {
        return hasEntry(equalTo("STR"), itemTextMatcher).matches(map);
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("with item content: ");
        itemTextMatcher.describeTo(description);
    }
};

Usas un BoundedMatcher como base porque solo coincide con objetos de tipo. Map Anula el método matchesSafely() y coloca el comparador encontrado. antes y la compara con una Matcher<String> que puedes pasar como argumento. Esto te permite llamar a withItemContent(equalTo("foo")). Para código brevedad, puedes crear otro comparador que ya llame a equalTo() y acepta un objeto String:

Kotlin

fun withItemContent(expectedText: String): Matcher<Object> {
    checkNotNull(expectedText)
    return withItemContent(equalTo(expectedText))
}

Java

public static Matcher<Object> withItemContent(String expectedText) {
    checkNotNull(expectedText);
    return withItemContent(equalTo(expectedText));
}

El código para hacer clic en el elemento es simple:

Kotlin

onData(withItemContent("item: 50")).perform(click())

Java

onData(withItemContent("item: 50")).perform(click());

Para obtener el código completo de esta prueba, consulta el método testClickOnItem50(). en el AdapterViewTest clase y esta LongListMatchers personalizada de búsqueda en GitHub.

Cómo hacer coincidir una vista secundaria específica

En el ejemplo anterior, se emite un clic en el medio de toda la fila de una ListView. ¿Qué ocurre si deseas realizar operaciones en un elemento secundario específico de la fila? Por ejemplo, si quieres hacer clic en la segunda columna de la fila LongListActivity, que muestra la longitud de cadena del contenido en la primera columna:

En este ejemplo, sería beneficioso extraer solo la longitud de
          un contenido en particular. Este proceso implica determinar
          de la segunda columna de una fila.

Solo agrega una especificación de onChildView() a tu implementación de DataInteraction

Kotlin

onData(withItemContent("item: 60"))
    .onChildView(withId(R.id.item_size))
    .perform(click())

Java

onData(withItemContent("item: 60"))
    .onChildView(withId(R.id.item_size))
    .perform(click());

Cómo interactuar con los elementos de la lista de la vista de reciclador

Los objetos RecyclerView funcionan de manera diferente que los objetos AdapterView, por lo que No se puede usar onData() para interactuar con ellos.

Para interactuar con RecyclerViews usando Espresso, puedes usar la espresso-contrib, que tiene una colección de RecyclerViewActions que se puede utilizar para desplazarse a posiciones o realizar acciones sobre los elementos:

  • scrollTo(): se desplaza hasta la vista coincidente, si existe.
  • scrollToHolder(): Se desplaza hasta el contenedor de vistas coincidente (si existe).
  • scrollToPosition(): se desplaza hasta una posición específica.
  • actionOnHolderItem(): realiza una acción de vista en un titular de vista coincidente.
  • actionOnItem(): realiza una acción de vista en una vista coincidente.
  • actionOnItemAtPosition(): realiza una acción de vista en la vista de una posición específica.

Los siguientes fragmentos incluyen algunos ejemplos del RecyclerViewSample muestra:

Kotlin

@Test(expected = PerformException::class)
fun itemWithText_doesNotExist() {
    // Attempt to scroll to an item that contains the special text.
    onView(ViewMatchers.withId(R.id.recyclerView))
        .perform(
            // scrollTo will fail the test if no item matches.
            RecyclerViewActions.scrollTo(
                hasDescendant(withText("not in the list"))
            )
        )
}

Java

@Test(expected = PerformException.class)
public void itemWithText_doesNotExist() {
    // Attempt to scroll to an item that contains the special text.
    onView(ViewMatchers.withId(R.id.recyclerView))
            // scrollTo will fail the test if no item matches.
            .perform(RecyclerViewActions.scrollTo(
                    hasDescendant(withText("not in the list"))
            ));
}

Kotlin

@Test fun scrollToItemBelowFold_checkItsText() {
    // First, scroll to the position that needs to be matched and click on it.
    onView(ViewMatchers.withId(R.id.recyclerView))
        .perform(
            RecyclerViewActions.actionOnItemAtPosition(
                ITEM_BELOW_THE_FOLD,
                click()
            )
        )

    // Match the text in an item below the fold and check that it's displayed.
    val itemElementText = "${activityRule.activity.resources
        .getString(R.string.item_element_text)} ${ITEM_BELOW_THE_FOLD.toString()}"
    onView(withText(itemElementText)).check(matches(isDisplayed()))
}

Java

@Test
public void scrollToItemBelowFold_checkItsText() {
    // First, scroll to the position that needs to be matched and click on it.
    onView(ViewMatchers.withId(R.id.recyclerView))
            .perform(RecyclerViewActions.actionOnItemAtPosition(ITEM_BELOW_THE_FOLD,
            click()));

    // Match the text in an item below the fold and check that it's displayed.
    String itemElementText = activityRule.getActivity().getResources()
            .getString(R.string.item_element_text)
            + String.valueOf(ITEM_BELOW_THE_FOLD);
    onView(withText(itemElementText)).check(matches(isDisplayed()));
}

Kotlin

@Test fun itemInMiddleOfList_hasSpecialText() {
    // First, scroll to the view holder using the isInTheMiddle() matcher.
    onView(ViewMatchers.withId(R.id.recyclerView))
        .perform(RecyclerViewActions.scrollToHolder(isInTheMiddle()))

    // Check that the item has the special text.
    val middleElementText = activityRule.activity.resources
            .getString(R.string.middle)
    onView(withText(middleElementText)).check(matches(isDisplayed()))
}

Java

@Test
public void itemInMiddleOfList_hasSpecialText() {
    // First, scroll to the view holder using the isInTheMiddle() matcher.
    onView(ViewMatchers.withId(R.id.recyclerView))
            .perform(RecyclerViewActions.scrollToHolder(isInTheMiddle()));

    // Check that the item has the special text.
    String middleElementText =
            activityRule.getActivity().getResources()
            .getString(R.string.middle);
    onView(withText(middleElementText)).check(matches(isDisplayed()));
}

Recursos adicionales

Para obtener más información sobre el uso de listas de Espresso en pruebas de Android, consulta el los siguientes recursos.

Ejemplos

  • DataAdapterSample: Presenta el punto de entrada onData() para Espresso, para las listas y los AdapterView objetos.