Listy espresso

Espresso oferuje mechanizmy, które umożliwiają przewinięcie listy lub wykonanie na niej działania w dwóch typy list: widoki adapterów i użytkowników funkcji recyklingu.

Gdy masz do czynienia z listami, zwłaszcza tymi utworzonymi za pomocą tagów RecyclerView lub AdapterView obiekt, który Cię interesuje, może być nawet ponieważ widoczna jest tylko niewielka liczba dzieci. poddawany recyklingowi podczas przewijania. W tym przypadku nie można użyć metody scrollTo() ponieważ wymaga istniejącego widoku.

Interakcja z elementami listy widoku adaptera

Zamiast używać metody onView(), rozpocznij wyszukiwanie od metody onData() i i dopasowujesz dane powiązane z widokiem, który chcesz dopasować. Espresso wyszuka wiersz w obiekcie Adapter i w widocznym obszarze.

Dopasowanie danych za pomocą dopasowania widoku niestandardowego

Poniższa aktywność zawiera pole ListView, które jest oparte na: SimpleAdapter z danymi każdego wiersza w obiekcie Map<String, Object>.

Działania na liście wyświetlane obecnie na ekranie zawierają listę z
          23 elementy. Każdy element ma numer zapisany w postaci ciągu znaków zmapowany na
          inny numer, który jest zapisywany jako obiekt.

Każda mapa zawiera 2 wpisy: klucz "STR" zawierający ciąg, taki jak "item: x" oraz klucz "LEN" zawierający Integer, który reprezentuje ich długość. Na przykład:

{"STR" : "item: 0", "LEN": 7}

Kod kliknięcia w wierszu z „item: 50”. wygląda tak:

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 przewija listę automatycznie w razie potrzeby.

Przyjrzyjmy się urządzeniu Matcher<Object> w środku onData(). Metoda is(instanceOf(Map.class)) zawęża wyszukiwanie do dowolnego elementu AdapterView, która jest wspierana przez obiekt Map.

W naszym przypadku ten aspekt zapytania pasuje do każdego wiersza widoku listy, ale chcemy kliknąć konkretny element, więc zawężamy wyszukiwanie dalej według tych kryteriów:

Kotlin

hasEntry(equalTo("STR"), `is`("item: 50"))

Java

hasEntry(equalTo("STR"), is("item: 50"))

Ten obiekt Matcher<String, Object> będzie pasować do każdej mapy zawierającej wpis z klucz "STR" i wartość "item: 50". Ponieważ kod służący do wyszukiwania Chcemy używać go w innych lokalizacjach, withItemContent dopasowanie do tego wyniku:

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);
    }
};

Używa się funkcji BoundedMatcher jako podstawy, ponieważ dopasowujemy tylko obiekty danego typu. Map Zastąp metodę matchesSafely(), dodając do znalezionego dopasowania wcześniej i dopasuj ją do parametru Matcher<String>, który można przekazać jako . Dzięki temu możesz zadzwonić pod numer withItemContent(equalTo("foo")). Aby uzyskać kod możesz utworzyć kolejne dopasowanie, które wywołuje już operatory equalTo() i akceptuje obiekt 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));
}

Teraz kod, który trzeba kliknąć, jest prosty:

Kotlin

onData(withItemContent("item: 50")).perform(click())

Java

onData(withItemContent("item: 50")).perform(click());

Pełny kod tego testu znajduje się w metodzie testClickOnItem50() w ciągu AdapterViewTest. klasa i ten niestandardowy LongListMatchers w usłudze GitHub.

Dopasowanie do konkretnego widoku podrzędnego

Powyższy przykład generuje kliknięcie w środku całego wiersza parametru ListView. Co jednak, jeśli chcemy przeprowadzić działanie na konkretnym elemencie podrzędnym wiersza? Na przykład możemy chce kliknąć drugą kolumnę w wierszu LongListActivity, , który wyświetla wartość String.length zawartości pierwszej kolumny:

W tym przykładzie dobrze byłoby wyodrębnić tylko długość
          konkretnego fragmentu treści. Ten proces obejmuje określenie
          wartości drugiej kolumny w wierszu.

Wystarczy, że dodasz specyfikację onChildView() do swojej implementacji 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());

Interakcja z elementami listy widoku recyklingu

Obiekty RecyclerView działają inaczej niż obiekty AdapterView, więc Nie możesz używać usługi onData() do interakcji z nimi.

Aby korzystać z obiektów RecyclerView za pomocą Espresso, możesz skorzystać z Pakiet espresso-contrib, który zawiera kolekcję RecyclerViewActions. które pozwalają przewijać elementy w wybrane miejsca lub wykonywać na nich różne czynności:

  • scrollTo() – powoduje przewijanie do pasującego widoku, jeśli istnieje.
  • scrollToHolder() – przewija stronę do pasującego właściciela widoku, jeśli istnieje.
  • scrollToPosition() – przewija do określonej pozycji.
  • actionOnHolderItem() – wykonuje czynność wyświetlenia w przypadku pasującego właściciela widoku.
  • actionOnItem() – wykonuje działanie związane z wyświetleniem pasującego widoku danych.
  • actionOnItemAtPosition() – wykonuje działania związane z widokiem danych w określonym miejscu.

Poniższe fragmenty kodu zawierają kilka przykładów RecyclerViewSample przykład:

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()));
}

Dodatkowe materiały

Więcej informacji o używaniu list Espresso w testach na Androidzie znajdziesz w poniższe zasoby.

Próbki

  • DataAdapterSample: Prezentuje punkt wejścia onData() do Espresso, listy i AdapterView obiektów.