Espresso 清單

Espresso 針對兩種類型的清單提供捲動或對特定項目執行操作的機制:轉接程式檢視畫面和回收器檢視畫面。

處理清單 (特別是使用 RecyclerViewAdapterView 物件建立的清單) 時,您感興趣的檢視畫面可能不會顯示在螢幕上,因為系統只會顯示少數子項,並在您捲動畫面時加以回收。在此情況下,無法使用 scrollTo() 方法,因為這個方法需要現有的檢視畫面。

與轉接程式檢視清單項目互動

請不要使用 onView() 方法,而是使用 onData() 開始搜尋,並針對用來比對的檢視畫面所支援的資料提供比對器。Espresso 會執行所有在 Adapter 物件中找到該資料列的工作,並在可視區域中顯示項目。

使用自訂檢視畫面比對器比對資料

以下活動包含 ListView,這個 SimpleAdapter 會保存 Map<String, Object> 物件中每個資料列的資料。

目前螢幕上所顯示的清單活動會包含包含 23 個項目的清單。每個項目都有一個數字 (以字串形式儲存) 對應至不同的數字,並改以物件的形式儲存。

每個對應都會包含兩個項目:一個包含字串 (例如 "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() 進入點。