Espresso 提供两种机制,用于滚动至特定项目或对其执行操作 列表类型:适配器视图和 recycler 视图。
在处理列表时,特别是使用 RecyclerView
或
AdapterView
对象,那么您感兴趣的视图可能并未出现在
因为只显示了少量的子项
系统会在您滚动页面时被回收在这种情况下不能使用 scrollTo()
方法
因为它需要使用现有的视图
与适配器视图列表项交互
请不要使用 onView()
方法,而是以 onData()
和
针对支持您想要匹配的视图的数据提供一个匹配器。
Espresso 将完成在 Adapter
对象中查找相应行的所有工作,
让相应项在视口中可见。
使用自定义视图匹配器来匹配数据
以下 activity 包含一个由 SimpleAdapter
支持的 ListView
用于存储 Map<String, Object>
对象中每行的数据。
每个映射都有两个条目:一个键 "STR"
,其中包含一个字符串,例如
"item: x"
和包含 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))
方法可将搜索范围缩小到
AdapterView
,由 Map
对象支持。
在本例中,查询的这一方面与列表视图的每一行都匹配,但我们 而您希望点击的具体内容,因此我们采用以下方式进一步缩小搜索范围:
Kotlin
hasEntry(equalTo("STR"), `is`("item: 50"))
Java
hasEntry(equalTo("STR"), is("item: 50"))
此 Matcher<String, Object>
将匹配包含具有如下内容的条目的任何映射:
键 "STR"
和值 "item: 50"
。由于用于查询
并且希望在其他位置重复使用它,我们就编写一个自定义
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());
如需查看此测试的完整代码,请查看 testClickOnItem50()
方法
内
AdapterViewTest
类和
这个自定义 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());
与回收器视图列表项交互
RecyclerView
对象的工作方式与 AdapterView
对象不同,因此
“onData()
”不能用于与之互动。
要使用 Espresso 与 RecyclerView 交互,您可以使用
espresso-contrib
软件包,其中包含一系列
RecyclerViewActions
可用于滚动至各个位置或对项目执行操作:
scrollTo()
- 滚动到匹配的视图(如果存在)。scrollToHolder()
- 滚动到匹配的视图持有者(如果存在)。scrollToPosition()
- 滚动到特定位置。actionOnHolderItem()
- 对匹配的视图持有者执行视图操作。actionOnItem()
- 对匹配的视图执行视图操作。actionOnItemAtPosition()
- 在特定位置对视图执行视图操作。
以下代码段是 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 中适用于列表和
AdapterView
的onData()
入口点 对象的操作。