Espresso 針對兩種類型的清單提供捲動或對特定項目執行操作的機制:轉接程式檢視畫面和回收器檢視畫面。
處理清單 (特別是使用 RecyclerView
或 AdapterView
物件建立的清單) 時,您感興趣的檢視畫面可能不會顯示在螢幕上,因為系統只會顯示少數子項,並在您捲動畫面時加以回收。在此情況下,無法使用 scrollTo()
方法,因為這個方法需要現有的檢視畫面。
與轉接程式檢視清單項目互動
請不要使用 onView()
方法,而是使用 onData()
開始搜尋,並針對用來比對的檢視畫面所支援的資料提供比對器。Espresso 會執行所有在 Adapter
物件中找到該資料列的工作,並在可視區域中顯示項目。
使用自訂檢視畫面比對器比對資料
以下活動包含 ListView
,這個 SimpleAdapter
會保存 Map<String, Object>
物件中每個資料列的資料。
每個對應都會包含兩個項目:一個包含字串 (例如 "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))
方法會將搜尋範圍縮小到 AdapterView
的任何項目,該項目由 Map
物件支援。
在這個範例中,這個查詢與清單檢視畫面的每一列相符,但我們想要特別點選某個項目,因此利用下列指令來進一步縮小搜尋範圍:
Kotlin
hasEntry(equalTo("STR"), `is`("item: 50"))
Java
hasEntry(equalTo("STR"), is("item: 50"))
如果對應項目包含具有 "STR"
鍵和 "item: 50"
值的項目,則 Matcher<String, Object>
會比對。由於要查詢的程式碼過長,且想在其他位置重複使用,因此我們針對該查詢編寫自訂的 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());
有關這項測試的完整程式碼,請查看 AdapterViewTest
類別中的 testClickOnItem50()
方法,以及 GitHub 上的這個自訂 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
物件的運作方式與 AdapterView
物件不同,因此 onData()
無法與這些物件互動。
如要使用 Espresso 與 RecyclerViews 互動,您可以使用 espresso-contrib
套件,其中包含的 RecyclerViewActions
集合,可用於捲動至位置或對項目執行動作:
scrollTo()
- 捲動至相符的檢視畫面 (如果有的話)。scrollToHolder()
- 捲動至相符的 View Holder (如果有的話)。scrollToPosition()
- 捲動至特定位置。actionOnHolderItem()
- 對相符的 View Holder 執行觀看動作。actionOnItem()
- 對相符的檢視畫面執行觀看動作。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:針對清單和
AdapterView
物件顯示 Espresso 的onData()
進入點。