רשימות להכנת אספרסו

ב-Espresso יש מנגנונים שאפשר לגלול או לבצע פעולות על פריט מסוים בשביל שני פריטים סוגי רשימות: תצוגות מתאמים ותצוגות של מיחזור.

כשמדובר ברשימות, במיוחד אלה שנוצרו באמצעות RecyclerView או AdapterView, יכול להיות שהתצוגה שמעניינת אותך לא תוצג אפילו במסך מכיוון שרק מספר קטן של ילדים מוצג לממוחזר תוך כדי הגלילה. במקרה הזה אי אפשר להשתמש ב-method scrollTo() כי הוא מחייב תצוגה קיימת.

אינטראקציה עם פריטים ברשימת התצוגות של המתאמים

במקום להשתמש בשיטה onView(), מתחילים את החיפוש עם onData() לספק התאמה לנתונים שמגבים את התצוגה שאליה רוצים להתאים. המערכת של Espresso תבצע את כל העבודה הבאה כדי למצוא את השורה באובייקט Adapter, הפיכת הפריט לגלוי באזור התצוגה.

התאמת נתונים באמצעות כלי להתאמה אישית של תצוגות מפורטות

הפעילות שבהמשך מכילה ListView, שמגובה על ידי SimpleAdapter שכולל את הנתונים של כל שורה באובייקט Map<String, Object>.

פעילות הרשימה שמוצגת כרגע על המסך מכילה רשימה עם
          23 פריטים. לכל פריט יש מספר המאוחסן כמחרוזת, וממופה אל
          מספר שונה, שנשמר כאובייקט.

בכל מפה יש שתי רשומות: מפתח "STR" שמכיל מחרוזת, כגון "item: x", ומפתח "LEN" שמכיל Integer, שמייצג את אורך התוכן. לדוגמה:

{"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 גולל ברשימה באופן אוטומטי לפי הצורך.

בואו נפרק את Matcher<Object> שבתוך onData(). השיטה 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 המותאם אישית הזה תואם ב-GitHub.

התאמה לתצוגת צאצא ספציפית

הדוגמה שלמעלה גורמת לקליק באמצע השורה כולה של 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() כדי לקיים איתם אינטראקציה.

כדי לבצע אינטראקציה עם RecyclerViews דרך Espresso, אפשר להשתמש חבילת espresso-contrib, עם אוסף של RecyclerViewActions שיכול לשמש כדי לגלול למיקומים או כדי לבצע פעולות על פריטים:

  • scrollTo() – גלילה לתצוגה התואמת, אם היא קיימת.
  • scrollToHolder() – גלילה לבעלים של התצוגה המפורטת, אם קיים.
  • scrollToPosition() – גלילה למיקום ספציפי.
  • actionOnHolderItem() – מבצעת פעולת צפייה בבעל צפייה תואם.
  • 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, אפשר לעיין ב במקורות המידע הבאים.

דוגמיות

  • DataAdapterSample: מציג את נקודת הכניסה onData() ל-Espresso, לרשימות ול-AdapterView אובייקטים.