Espressolisten

Espresso bietet Mechanismen zum Scrollen oder Bearbeiten eines bestimmten Elements für zwei Arten von Listen: Adapteransichten und Recycler-Ansichten.

Bei Listen, insbesondere solchen, die mit einem RecyclerView- oder AdapterView-Objekt erstellt wurden, ist die für Sie relevante Ansicht unter Umständen gar nicht auf dem Bildschirm zu sehen, da nur eine kleine Anzahl von untergeordneten Elementen angezeigt wird, die beim Scrollen sichtbar sind. Die Methode scrollTo() kann in diesem Fall nicht verwendet werden, da dafür eine vorhandene Ansicht erforderlich ist.

Mit Elementen aus der Adapteransicht interagieren

Beginnen Sie Ihre Suche anstelle der Methode onView() mit onData() und stellen Sie einen Abgleich für die Daten bereit, die die Ansicht sichern, die Sie abgleichen möchten. Espresso sucht die Zeile im Adapter-Objekt und macht das Element im Darstellungsbereich sichtbar.

Daten mithilfe eines Abgleichs der benutzerdefinierten Ansicht abgleichen

Die folgende Aktivität enthält ein ListView, das von einem SimpleAdapter unterstützt wird, das Daten für jede Zeile in einem Map<String, Object>-Objekt enthält.

Die derzeit auf dem Bildschirm angezeigte Listenaktivität enthält eine Liste mit 23 Einträgen. Jedes Element hat eine Nummer, die als String gespeichert und einer anderen Zahl zugeordnet ist. Diese wird stattdessen als Objekt gespeichert.

Jede Zuordnung hat zwei Einträge: einen Schlüssel "STR" mit einem String wie "item: x" und einen Schlüssel "LEN" mit einem Integer für die Länge des Inhalts. Beispiele:

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

Der Code für einen Klick auf die Zeile mit „item: 50“ sieht wie folgt aus:

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

Beachte, dass Espresso bei Bedarf automatisch durch die Liste scrollt.

Nehmen wir das Matcher<Object> im Inneren von onData() auseinander. Die Methode is(instanceOf(Map.class)) schränkt die Suche auf ein beliebiges Element des AdapterView ein, das durch ein Map-Objekt unterstützt wird.

In unserem Fall stimmt dieser Aspekt der Abfrage mit jeder Zeile der Listenansicht überein, aber wir möchten nur auf ein Element klicken. Daher grenzen wir die Suche weiter ein mit:

Kotlin

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

Java

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

Diese Matcher<String, Object> entspricht jeder Karte, die einen Eintrag mit dem Schlüssel "STR" und dem Wert "item: 50" enthält. Da der Code zum Nachschlagen lang ist und wir ihn an anderen Orten wiederverwenden möchten, schreiben wir dafür einen benutzerdefinierten withItemContent-Matcher:

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

Sie verwenden BoundedMatcher als Basis, da nur Objekte vom Typ Map abgeglichen werden sollen. Überschreiben Sie die Methode matchesSafely(), indem Sie den zuvor gefundenen Abgleich mit einer Matcher<String> verknüpfen, die Sie als Argument übergeben können. Dadurch kannst du withItemContent(equalTo("foo")) aufrufen. Zur Vereinfachung des Codes können Sie einen weiteren Abgleich erstellen, der bereits equalTo() aufruft und ein String-Objekt akzeptiert:

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

Jetzt ist der Code zum Klicken auf das Element einfach:

Kotlin

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

Java

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

Den vollständigen Code dieses Tests finden Sie in der Methode testClickOnItem50() in der Klasse AdapterViewTest und in diesem benutzerdefinierten Abgleich LongListMatchers auf GitHub.

Bestimmte Kinderansicht abgleichen

Das obige Beispiel gibt einen Klick in die Mitte der gesamten Zeile eines ListView aus. Aber was ist, wenn wir ein bestimmtes untergeordnetes Element der Zeile bearbeiten möchten? Wir möchten beispielsweise auf die zweite Spalte der Zeile von LongListActivity klicken, in der die String.length des Inhalts in der ersten Spalte angezeigt wird:

In diesem Beispiel wäre es von Vorteil, nur die Länge eines bestimmten Inhalts zu extrahieren. Bei diesem Vorgang wird der Wert der zweiten Spalte in einer Zeile ermittelt.

Fügen Sie Ihrer Implementierung von DataInteraction einfach eine onChildView()-Spezifikation hinzu:

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

Mit Elementen aus der Recyclingansicht interagieren

RecyclerView-Objekte funktionieren anders als AdapterView-Objekte, daher kann onData() nicht für die Interaktion mit ihnen verwendet werden.

Für die Interaktion mit RecyclerViews über Espresso können Sie das Paket espresso-contrib verwenden, das eine Sammlung von RecyclerViewActions enthält. Damit können Sie zu Positionen scrollen oder Aktionen für Elemente ausführen:

  • scrollTo(): Scrollt zur übereinstimmenden Ansicht, falls vorhanden.
  • scrollToHolder(): Scrollt zum übereinstimmenden Inhaber der Datenansicht, falls vorhanden.
  • scrollToPosition(): Scrollt an eine bestimmte Position.
  • actionOnHolderItem(): führt eine Aufrufaktion für einen übereinstimmenden Datenansichtsinhaber aus.
  • actionOnItem(): führt eine Aufrufaktion für eine übereinstimmende Ansicht aus.
  • actionOnItemAtPosition(): führt eine ViewAction für eine Ansicht an einer bestimmten Position aus.

Die folgenden Snippets enthalten einige Beispiele aus dem RecyclerViewSample-Beispiel:

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

Weitere Informationen

Weitere Informationen zur Verwendung von Espresso-Listen in Android-Tests finden Sie in den folgenden Ressourcen.

Produktproben

  • DataAdapterSample: Zeigt den Einstiegspunkt onData() für Espresso für Listen und AdapterView-Objekte an.