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:

KotlinJava
onData(allOf(`is`(instanceOf(Map::class.java)), hasEntry(equalTo("STR"),
       
`is`("item: 50")))).perform(click())
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:

KotlinJava
hasEntry(equalTo("STR"), `is`("item: 50"))
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:

KotlinJava
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)
   
}
}
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:

KotlinJava
fun withItemContent(expectedText: String): Matcher<Object> {
    checkNotNull
(expectedText)
   
return withItemContent(equalTo(expectedText))
}
public static Matcher<Object> withItemContent(String expectedText) {
    checkNotNull
(expectedText);
   
return withItemContent(equalTo(expectedText));
}

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

KotlinJava
onData(withItemContent("item: 50")).perform(click())
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:

KotlinJava
onData(withItemContent("item: 60"))
   
.onChildView(withId(R.id.item_size))
   
.perform(click())
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:

KotlinJava
@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"))
           
)
       
)
}
@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"))
           
));
}
KotlinJava
@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()))
}
@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()));
}
KotlinJava
@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()))
}
@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.