Espresso lists
Stay organized with collections
Save and categorize content based on your preferences.
Espresso offers mechanisms to scroll to or act on a particular item for two types of lists: adapter views and recycler views.
When dealing with lists, especially those created with a RecyclerView or an
AdapterView object, the view that you’re interested in might not even be on
the screen because only a small number of children are displayed and are
recycled as you scroll. The scrollTo() method can’t be used in this case
because it requires an existing view.
Interact with adapter view list items
Instead of using the onView() method, start your search with onData() and
provide a matcher against the data that is backing the view you’d like to match.
Espresso will do all the work of finding the row in the Adapter object and
making the item visible in the viewport.
Match data using a custom view matcher
The activity below contains a ListView, which is backed by a SimpleAdapter
that holds data for each row in a Map<String, Object> object.
The list activity currently shown on the screen contains a list with 23 items. Each item has a number, stored as a String, mapped to a different number, which is stored as an Object instead.
Each map has two entries: a key "STR" that contains a String, such as
"item: x", and a key "LEN" that contains an Integer, which represents the
length of the content. For example:
{"STR" : "item: 0", "LEN": 7}
The code for a click on the row with "item: 50" looks like this:
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());
Note that Espresso scrolls through the list automatically as needed.
Let’s take apart the Matcher<Object> inside onData(). The
is(instanceOf(Map.class)) method narrows the search to any item of the
AdapterView, which is backed by a Map object.
In our case, this aspect of the query matches every row of the list view, but we want to click specifically on an item, so we narrow the search further with:
Kotlin
hasEntry(equalTo("STR"),`is`("item: 50"))
Java
hasEntry(equalTo("STR"),is("item: 50"))
This Matcher<String, Object> will match any Map that contains an entry with
the key "STR" and the value "item: 50". Because the code to look up this is
long and we want to reuse it in other locations, let’s write a custom
withItemContent matcher for that:
Kotlin
returnobject:BoundedMatcher<Object,Map>(Map::class.java){ overridefunmatchesSafely(map:Map):Boolean{ returnhasEntry(equalTo("STR"),itemTextMatcher).matches(map) } overridefundescribeTo(description:Description){ description.appendText("with item content: ") itemTextMatcher.describeTo(description) } }
Java
returnnewBoundedMatcher<Object,Map>(Map.class){ @Override publicbooleanmatchesSafely(Mapmap){ returnhasEntry(equalTo("STR"),itemTextMatcher).matches(map); } @Override publicvoiddescribeTo(Descriptiondescription){ description.appendText("with item content: "); itemTextMatcher.describeTo(description); } };
You use a BoundedMatcher as a base because to only match objects of type
Map. Override the matchesSafely() method, putting in the matcher found
earlier, and match it against a Matcher<String> that you can pass as an
argument. This allows you to call withItemContent(equalTo("foo")). For code
brevity, you can create another matcher that already calls the equalTo() and
accepts a String object:
Kotlin
funwithItemContent(expectedText:String):Matcher<Object>{ checkNotNull(expectedText) returnwithItemContent(equalTo(expectedText)) }
Java
publicstaticMatcher<Object>withItemContent(StringexpectedText){ checkNotNull(expectedText); returnwithItemContent(equalTo(expectedText)); }
Now the code to click on the item is simple:
Kotlin
onData(withItemContent("item: 50")).perform(click())
Java
onData(withItemContent("item: 50")).perform(click());
For the full code of this test, take a look at the testClickOnItem50() method
within the
AdapterViewTest
class and
this custom LongListMatchers
matcher on GitHub.
Match a specific child view
The sample above issues a click in the middle of the entire row of a ListView.
But what if we want to operate on a specific child of the row? For example, we
would like to click on the second column of the row of the LongListActivity,
which displays the String.length of the content in the first column:
In this example, it would be beneficial to extract just the length of a particular piece of content. This process involves determining the value of the second column in a row.
Just add an onChildView() specification to your implementation of
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());
Interact with recycler view list items
RecyclerView objects work differently than AdapterView objects, so
onData() cannot be used to interact with them.
To interact with RecyclerViews using Espresso, you can use the
espresso-contrib package, which has a collection of
RecyclerViewActions
that can be used to scroll to positions or to perform actions on items:
scrollTo()- Scrolls to the matched View, if it exists.scrollToHolder()- Scrolls to the matched View Holder, if it exists.scrollToPosition()- Scrolls to a specific position.actionOnHolderItem()- Performs a View Action on a matched View Holder.actionOnItem()- Performs a View Action on a matched View.actionOnItemAtPosition()- Performs a ViewAction on a view at a specific position.
The following snippets feature some examples from the RecyclerViewSample sample:
Kotlin
@Test(expected=PerformException::class) funitemWithText_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) publicvoiditemWithText_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
@TestfunscrollToItemBelowFold_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. valitemElementText="${activityRule.activity.resources .getString(R.string.item_element_text)}${ITEM_BELOW_THE_FOLD.toString()}" onView(withText(itemElementText)).check(matches(isDisplayed())) }
Java
@Test publicvoidscrollToItemBelowFold_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. StringitemElementText=activityRule.getActivity().getResources() .getString(R.string.item_element_text) +String.valueOf(ITEM_BELOW_THE_FOLD); onView(withText(itemElementText)).check(matches(isDisplayed())); }
Kotlin
@TestfunitemInMiddleOfList_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. valmiddleElementText=activityRule.activity.resources .getString(R.string.middle) onView(withText(middleElementText)).check(matches(isDisplayed())) }
Java
@Test publicvoiditemInMiddleOfList_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. StringmiddleElementText= activityRule.getActivity().getResources() .getString(R.string.middle); onView(withText(middleElementText)).check(matches(isDisplayed())); }
Additional resources
For more information about using Espresso lists in Android tests, consult the following resources.
Samples
- DataAdapterSample:
Showcases the
onData()entry point for Espresso, for lists andAdapterViewobjects.