Espresso 为两种类型的列表(即适配器视图和回收器视图)提供了滚动到特定项目或对特定项目执行操作的机制。
在处理列表(尤其是使用 RecyclerView 或 AdapterView 对象创建的列表)时,您想要查看的视图甚至可能不在屏幕上,因为当您滚动屏幕时,只会显示和回收少量的子视图。在这种情况下,不能使用 scrollTo() 方法,因为它需要一个现有的视图。
与适配器视图列表项交互
在您的搜索中先使用 onData() 方法(而不是使用 onView() 方法),并针对支持您想要匹配的视图的数据提供一个匹配器。Espresso 将完成在 Adapter 对象中查找相应行并使相应项目在视口中可见的所有工作。
使用自定义视图匹配器来匹配数据
下面的 Activity 包含一个 ListView,该视图由一个 SimpleAdapter 支持,该适配器将每一行的数据保存在一个 Map 对象中。
每个映射都有两个条目:一个键 "STR",它包含一个字符串,如 "item: x";一个键 "LEN",它包含一个 Integer,该整数表示内容的长度。例如:
{"STR" : "item: 0", "LEN": 7}
要点击包含“item: 50”的行,代码如下所示:
Kotlin
onData(allOf(`is`(instanceOf(Map.class)), 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。is(instanceOf(Map.class)) 方法将搜索范围缩小到由 Map 对象支持的 AdapterView 的任何项目。
针对我们目前讨论的情况,查询的这一方面与列表视图的每一行都匹配,但我们要专门点击一个项目,因此我们通过以下代码来进一步缩小搜索范围:
Kotlin
hasEntry(equalTo("STR"), `is`("item: 50"))Java
hasEntry(equalTo("STR"), is("item: 50"))
此 Matcher 将与包含一个具有键 "STR" 和值 "item: 50" 的条目的任何映射匹配。因为查询此内容的代码很长并且我们希望在其他位置重复使用该代码,所以我们来为此编写一个自定义 withItemContent 匹配器:
Kotlin
return object : BoundedMatcher(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(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 进行匹配。这样您就可以调用 withItemContent(equalTo("foo")) 了。为了代码简便起见,您可以再创建一个已调用 equalTo() 且接受 String 对象的匹配器:
Kotlin
fun withItemContent(expectedText: String): Matcher {
checkNotNull(expectedText)
return withItemContent(equalTo(expectedText))
}Java
public static Matcher withItemContent(String expectedText) {
checkNotNull(expectedText);
return withItemContent(equalTo(expectedText));
}
现在,要点击该项目,代码非常简单:
Kotlin
onData(withItemContent("item: 50")).perform(click())Java
onData(withItemContent("item: 50")).perform(click());
如需查看此测试的完整代码,请查看 AdapterViewTest 类中的 testClickOnItem50() 方法以及 GitHub 上的此自定义 LongListMatchers 匹配器。
匹配特定子视图
上面的示例会在 ListView 的整行中间发出一次点击。但是,如果我们要对行的特定子级执行操作,该怎么办呢?例如,我们想要点击 LongListActivity 行的第二列,该列显示第一列中内容的 String.length:
只需将 onChildView() 规范添加到您的 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());
注意:本例使用了上面示例中的 withItemContent() 匹配器。请查看 GitHub 上的 AdapterViewTest 类中的 testClickOnSpecificChildOfRow60() 方法。
与回收器视图列表项交互
RecyclerView 对象的工作方式与 AdapterView 对象不同,因此不能使用 onData() 方法与其交互。
要使用 Espresso 与 RecyclerView 交互,您可以使用 espresso-contrib 软件包,该软件包具有 RecyclerViewActions 的集合,可用于滚动到相应位置或对项目执行操作:
scrollTo() - 滚动到匹配的视图。
scrollToHolder() - 滚动到匹配的数据视图持有者。
scrollToPosition() - 滚动到特定位置。
actionOnHolderItem() - 对匹配的视图持有者执行视图操作。
actionOnItem() - 对匹配的视图执行视图操作。
actionOnItemAtPosition() - 在特定位置对视图执行视图操作。
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 中适用于列表和 AdapterView 对象的 onData() 入口点。