对于 Adapter 视图(Adapter views)和 Recycler 视图(Recycler views)这两种类型的列表,Espresso 提供了滚动到特定项或对特定项执行操作的机制。
在处理列表(尤其是由 RecyclerView 或 AdapterView 对象创建的列表)时,您感兴趣的视图可能甚至不在屏幕上,因为屏幕上仅显示少量子项,并且会在您滚动时进行回收。在这种情况下,无法使用 scrollTo() 方法,因为它要求视图必须存在。
与 Adapter 视图列表项交互
不要使用 onView() 方法,而应改用 onData() 开始搜索,并针对支持您要匹配的视图的数据提供匹配器。Espresso 将完成在 Adapter 对象中查找该行并将该项在视口中显示出来的所有工作。
使用自定义视图匹配器匹配数据
下方的 Activity 包含一个 ListView,它由一个 SimpleAdapter 支持,该适配器将每一行的数据保存在一个 Map<String, Object> 对象中。

每个 Map 都有两个条目:一个包含字符串(例如 "item: x")的键 "STR",以及一个包含 Integer(代表内容的长度)的键 "LEN"。例如
{"STR" : "item: 0", "LEN": 7}
点击“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());
请注意,Espresso 会根据需要自动滚动列表。
让我们分析一下 onData() 内部的 Matcher<Object>。is(instanceOf(Map.class)) 方法将搜索范围缩小到由 Map 对象支持的 AdapterView 的任何项。
在我们的例子中,查询的这一部分匹配列表视图的每一行,但我们只想点击特定的某一项,因此我们通过以下方式进一步缩小搜索范围
Kotlin
hasEntry(equalTo("STR"), `is`("item: 50"))
Java
hasEntry(equalTo("STR"), is("item: 50"))
此 Matcher<String, Object> 将匹配任何包含键为 "STR" 且值为 "item: 50" 的条目的 Map。由于查找此项的代码很长,且我们希望在其他地方复用它,让我们为此编写一个自定义的 withItemContent 匹配器
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); } };
您可以使用 BoundedMatcher 作为基础,因为它仅匹配类型为 Map 的对象。重写 matchesSafely() 方法,放入之前找到的匹配器,并将其与您可以作为参数传递的 Matcher<String> 进行匹配。这允许您调用 withItemContent(equalTo("foo"))。为了代码简洁,您可以创建另一个已经调用 equalTo() 并接受 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)); }
现在点击该项的代码变得很简单
Kotlin
onData(withItemContent("item: 50")).perform(click())
Java
onData(withItemContent("item: 50")).perform(click());
有关此测试的完整代码,请查看 GitHub 上 AdapterViewTest 类中的 testClickOnItem50() 方法,以及这个自定义的 LongListMatchers 匹配器。
匹配特定的子视图
上面的示例在 ListView 的整个行的中间执行了点击操作。但如果我们想对该行中的特定子视图进行操作该怎么办?例如,我们想点击 LongListActivity 行的第二列,该列显示了第一列内容的 String.length

只需在您的 DataInteraction 实现中添加一个 onChildView() 规范即可
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());
与 RecyclerView 列表项交互
RecyclerView 对象的工作方式与 AdapterView 对象不同,因此不能使用 onData() 与它们进行交互。
要使用 Espresso 与 RecyclerView 进行交互,您可以使用 espresso-contrib 软件包,其中包含一系列 RecyclerViewActions,可用于滚动到指定位置或对列表项执行操作
scrollTo()- 滚动到匹配的 View(如果存在)。scrollToHolder()- 滚动到匹配的 View Holder(如果存在)。scrollToPosition()- 滚动到特定位置。actionOnHolderItem()- 对匹配的 View Holder 执行 View Action。actionOnItem()- 对匹配的 View 执行 View Action。actionOnItemAtPosition()- 对特定位置的视图执行 ViewAction。
以下代码片段展示了 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())); }
其他资源
有关在 Android 测试中使用 Espresso 列表的更多信息,请查阅以下资源。
示例
- DataAdapterSample:展示了 Espresso 的
onData()入口点,适用于列表和AdapterView对象。