Espresso ofrece mecanismos para desplazarse o actuar sobre un elemento específico para dos tipos de listas: vistas de adaptador y vistas de reciclador.
Cuando trabajas con listas, en especial aquellas creadas con un objeto RecyclerView
o AdapterView
, es posible que la vista que te interesa ni siquiera aparezca en la pantalla porque solo se muestra una pequeña cantidad de elementos secundarios y se reciclan a medida que te desplazas. En este caso, no se puede usar el método scrollTo()
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 la búsqueda con onData()
y proporciona un comparador con los datos que respaldan la vista que quieres.
Espresso hará todo el trabajo de encontrar la fila en el objeto Adapter
y hacer que el elemento sea visible en el viewport.
Cómo hacer coincidir los datos mediante un buscador de coincidencias de vistas personalizado
La actividad que se muestra a continuación contiene un ListView
, respaldado por un SimpleAdapter
que contiene datos para cada fila en un objeto Map<String, Object>
.
Cada mapa tiene dos entradas: una clave "STR"
que contiene una cadena, como "item: x"
, y una clave "LEN"
que contiene un Integer
, que representa la longitud 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 método is(instanceOf(Map.class))
reduce la búsqueda a cualquier elemento de 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 queremos hacer clic específicamente en un elemento, por lo que acotamos aún más la búsqueda de la siguiente manera:
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 largo y queremos reutilizarlo en otras ubicaciones, escribiremos un comparador withItemContent
personalizado 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()
, coloca el comparador que se encontró antes y hazlo coincidir con un Matcher<String>
que puedas pasar como argumento. Esto te permite llamar a withItemContent(equalTo("foo"))
. Para abreviar el código, puedes crear otro comparador que ya llame a equalTo()
y acepte 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()
dentro de la clase AdapterViewTest
y este comparador LongListMatchers
personalizado 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, nos gustaría hacer clic en la segunda columna de la fila del LongListActivity
, que muestra la longitud de la string del contenido en la primera columna:
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 el paquete espresso-contrib
, que tiene una colección de RecyclerViewActions
que se puede usar para desplazarse a posiciones o realizar acciones en los elementos:
scrollTo()
: Se desplaza hasta la vista coincidente, si existe.scrollToHolder()
: Se desplaza hasta el contenedor de la vista 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.
En los siguientes fragmentos, se muestran algunos ejemplos del ejemplo de RecyclerViewSample:
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 los siguientes recursos.
Ejemplos
- DataAdapterSample: muestra el punto de entrada de
onData()
para Espresso, para listas y objetosAdapterView
.