Espressorezepte

In diesem Dokument wird die Einrichtung einer Vielzahl gängiger Espresso-Tests beschrieben.

Ansicht neben einer anderen Ansicht abgleichen

Ein Layout könnte bestimmte Ansichten enthalten, die an sich nicht eindeutig sind. Für kann eine Schaltfläche für einen wiederkehrenden Anruf in einer Tabelle mit Kontakten die gleiche R.id enthalten, enthalten denselben Text und dieselben Eigenschaften wie andere Aufrufe. innerhalb der Ansichtshierarchie.

In dieser Aktivität wird die Ansicht mit dem Text "7" beispielsweise über mehrere Zeilen:

Eine Listenaktivität, bei der drei Kopien desselben Ansichtselements angezeigt werden
     in einer Liste mit drei Elementen

Häufig wird der nicht eindeutigen Ansicht ein eindeutiges Label zugeordnet, hinzugefügt, etwa der Name des Kontakts neben der Anrufschaltfläche. In diesem Fall können Sie die Auswahl mit dem Matcher hasSibling() eingrenzen:

Kotlin

onView(allOf(withText("7"), hasSibling(withText("item: 0"))))
    .perform(click())

Java

onView(allOf(withText("7"), hasSibling(withText("item: 0"))))
    .perform(click());

Einer Ansicht innerhalb einer Aktionsleiste zuordnen

ActionBarTestActivity hat zwei verschiedene Aktionsleisten: eine normale Aktionsleiste und einer kontextbezogenen Aktionsleiste, die über ein Optionsmenü erstellt wird. Beide enthält ein Element, das immer sichtbar ist, und zwei Elemente, im Dreipunkt-Menü angezeigt. Wenn auf ein Element geklickt wird, ändert es eine TextView in die Inhalt des angeklickten Elements

Die Zuordnung der sichtbaren Symbole in beiden Aktionsleisten ist ganz einfach. im folgenden Code-Snippet einfügen:

Kotlin

fun testClickActionBarItem() {
    // We make sure the contextual action bar is hidden.
    onView(withId(R.id.hide_contextual_action_bar))
        .perform(click())

    // Click on the icon - we can find it by the r.Id.
    onView(withId(R.id.action_save))
        .perform(click())

    // Verify that we have really clicked on the icon
    // by checking the TextView content.
    onView(withId(R.id.text_action_bar_result))
        .check(matches(withText("Save")))
}

Java

public void testClickActionBarItem() {
    // We make sure the contextual action bar is hidden.
    onView(withId(R.id.hide_contextual_action_bar))
        .perform(click());

    // Click on the icon - we can find it by the r.Id.
    onView(withId(R.id.action_save))
        .perform(click());

    // Verify that we have really clicked on the icon
    // by checking the TextView content.
    onView(withId(R.id.text_action_bar_result))
        .check(matches(withText("Save")));
}

Die Schaltfläche „Speichern“ befindet sich in der Aktionsleiste oben in der Aktivität

Der Code für die kontextbezogene Aktionsleiste sieht identisch aus:

Kotlin

fun testClickActionModeItem() {
    // Make sure we show the contextual action bar.
    onView(withId(R.id.show_contextual_action_bar))
        .perform(click())

    // Click on the icon.
    onView((withId(R.id.action_lock)))
        .perform(click())

    // Verify that we have really clicked on the icon
    // by checking the TextView content.
    onView(withId(R.id.text_action_bar_result))
        .check(matches(withText("Lock")))
}

Java

public void testClickActionModeItem() {
    // Make sure we show the contextual action bar.
    onView(withId(R.id.show_contextual_action_bar))
        .perform(click());

    // Click on the icon.
    onView((withId(R.id.action_lock)))
        .perform(click());

    // Verify that we have really clicked on the icon
    // by checking the TextView content.
    onView(withId(R.id.text_action_bar_result))
        .check(matches(withText("Lock")));
}

Die Schaltfläche zum Sperren befindet sich in der Aktionsleiste oben in der Aktivität

Für die normale Aktion ist es etwas komplizierter, auf Elemente im Dreipunkt-Menü zu klicken. da einige Geräte über eine Schaltfläche für das Hardware-Dreipunkt-Menü verfügen, über die und bei einigen Geräten wird ein Software-Overflow angezeigt. um ein normales Dreipunkt-Menü zu öffnen. Glücklicherweise übernimmt Espresso für uns.

Für die normale Aktionsleiste:

Kotlin

fun testActionBarOverflow() {
    // Make sure we hide the contextual action bar.
    onView(withId(R.id.hide_contextual_action_bar))
        .perform(click())

    // Open the options menu OR open the overflow menu, depending on whether
    // the device has a hardware or software overflow menu button.
    openActionBarOverflowOrOptionsMenu(
            ApplicationProvider.getApplicationContext<Context>())

    // Click the item.
    onView(withText("World"))
        .perform(click())

    // Verify that we have really clicked on the icon by checking
    // the TextView content.
    onView(withId(R.id.text_action_bar_result))
        .check(matches(withText("World")))
}

Java

public void testActionBarOverflow() {
    // Make sure we hide the contextual action bar.
    onView(withId(R.id.hide_contextual_action_bar))
        .perform(click());

    // Open the options menu OR open the overflow menu, depending on whether
    // the device has a hardware or software overflow menu button.
    openActionBarOverflowOrOptionsMenu(
            ApplicationProvider.getApplicationContext());

    // Click the item.
    onView(withText("World"))
        .perform(click());

    // Verify that we have really clicked on the icon by checking
    // the TextView content.
    onView(withId(R.id.text_action_bar_result))
        .check(matches(withText("World")));
}

Die Schaltfläche für das Dreipunkt-Menü ist sichtbar und eine Liste wird unterhalb des
          Aktionsleiste oben auf dem Bildschirm

Auf Geräten mit einer Schaltfläche für das Hardware-Dreipunkt-Menü sieht das so aus:

Es gibt keine Schaltfläche für das Dreipunkt-Menü und am unteren Rand wird eine Liste angezeigt.
          des Bildschirms,

Bei der kontextbezogenen Aktionsleiste geht das ganz einfach:

Kotlin

fun testActionModeOverflow() {
    // Show the contextual action bar.
    onView(withId(R.id.show_contextual_action_bar))
        .perform(click())

    // Open the overflow menu from contextual action mode.
    openContextualActionModeOverflowMenu()

    // Click on the item.
    onView(withText("Key"))
        .perform(click())

    // Verify that we have really clicked on the icon by
    // checking the TextView content.
    onView(withId(R.id.text_action_bar_result))
        .check(matches(withText("Key")))
    }
}

Java

public void testActionModeOverflow() {
    // Show the contextual action bar.
    onView(withId(R.id.show_contextual_action_bar))
        .perform(click());

    // Open the overflow menu from contextual action mode.
    openContextualActionModeOverflowMenu();

    // Click on the item.
    onView(withText("Key"))
        .perform(click());

    // Verify that we have really clicked on the icon by
    // checking the TextView content.
    onView(withId(R.id.text_action_bar_result))
        .check(matches(withText("Key")));
    }
}

Die Schaltfläche für das Dreipunkt-Menü wird in der Aktionsleiste angezeigt und die Liste der
          werden unterhalb der Aktionsleiste oben auf dem Bildschirm angezeigt.

Den vollständigen Code für diese Beispiele finden Sie in der ActionBarTest.java-Beispiel auf GitHub

Versichern, dass eine Ansicht nicht angezeigt wird

Nachdem Sie eine Reihe von Aktionen ausgeführt haben, Status der zu testenden UI. Manchmal ist dies ein negativer Fall, z. B. wenn etwas nicht passiert. Sie können jede beliebige Hamcrest-Ansicht Matcher mit ViewAssertions.matches() in ein ViewAssertion-Objekt umgewandelt.

Im folgenden Beispiel nehmen wir den isDisplayed()-Matcher und kehren ihn um: Standard-not()-Matcher:

Kotlin

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.hamcrest.Matchers.not

onView(withId(R.id.bottom_left))
    .check(matches(not(isDisplayed())))

Java

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.Matchers.not;

onView(withId(R.id.bottom_left))
    .check(matches(not(isDisplayed())));

Der obige Ansatz funktioniert, wenn die Ansicht immer noch Teil der Hierarchie ist. Falls ja nicht, erhalten Sie eine NoMatchingViewException und müssen ViewAssertions.doesNotExist().

Sicherstellen, dass keine Ansicht vorhanden ist

Wenn die Ansicht aus der Ansichtshierarchie entfernt wurde, was passiert, wenn ein Aktion zu einem Wechsel zu einer anderen Aktivität geführt. Verwenden Sie ViewAssertions.doesNotExist():

Kotlin

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.matcher.ViewMatchers.withId

onView(withId(R.id.bottom_left))
    .check(doesNotExist())

Java

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.matcher.ViewMatchers.withId;

onView(withId(R.id.bottom_left))
    .check(doesNotExist());

Bestätigen, dass sich ein Datenelement nicht in einem Adapter befindet

Um nachzuweisen, dass sich ein bestimmtes Datenelement nicht in einem AdapterView befindet, müssen Sie Folgendes tun: etwas anders machen. Wir müssen die AdapterView finden, die uns interessieren und die in ihrem Besitz befindlichen Daten abfragen. Wir brauchen onData() nicht. Stattdessen verwenden wir onView(), um den AdapterView zu finden, und verwenden dann einen anderen um die Daten in der Ansicht zu bearbeiten.

Zuerst zum Abgleich:

Kotlin

private fun withAdaptedData(dataMatcher: Matcher<Any>): Matcher<View> {
    return object : TypeSafeMatcher<View>() {

        override fun describeTo(description: Description) {
            description.appendText("with class name: ")
            dataMatcher.describeTo(description)
        }

        public override fun matchesSafely(view: View) : Boolean {
            if (view !is AdapterView<*>) {
                return false
            }

            val adapter = view.adapter
            for (i in 0 until adapter.count) {
                if (dataMatcher.matches(adapter.getItem(i))) {
                    return true
                }
            }

            return false
        }
    }
}

Java

private static Matcher<View> withAdaptedData(final Matcher<Object> dataMatcher) {
    return new TypeSafeMatcher<View>() {

        @Override
        public void describeTo(Description description) {
            description.appendText("with class name: ");
            dataMatcher.describeTo(description);
        }

        @Override
        public boolean matchesSafely(View view) {
            if (!(view instanceof AdapterView)) {
                return false;
            }

            @SuppressWarnings("rawtypes")
            Adapter adapter = ((AdapterView) view).getAdapter();
            for (int i = 0; i < adapter.getCount(); i++) {
                if (dataMatcher.matches(adapter.getItem(i))) {
                    return true;
                }
            }

            return false;
        }
    };
}

Dann brauchen wir nur onView(), um AdapterView zu finden:

Kotlin

fun testDataItemNotInAdapter() {
    onView(withId(R.id.list))
          .check(matches(not(withAdaptedData(withItemContent("item: 168")))))
    }
}

Java

@SuppressWarnings("unchecked")
public void testDataItemNotInAdapter() {
    onView(withId(R.id.list))
          .check(matches(not(withAdaptedData(withItemContent("item: 168")))));
    }
}

Es gibt eine Assertion, die fehlschlägt, wenn ein Artikel gleich „item: 168“ ist. ist in einer Adapteransicht mit der ID-Liste vorhanden.

Das vollständige Beispiel finden Sie in der testDataItemNotInAdapter()-Methode im AdapterViewTest.java auf GitHub.

Benutzerdefinierten Fehler-Handler verwenden

Wenn Sie die Standardeinstellung FailureHandler in Espresso durch eine benutzerdefinierte ersetzen, ist Folgendes möglich: zusätzliche oder andere Fehlerbehandlungsmethoden, wie z. B. das Erstellen eines Screenshots oder das Übergeben von sowie zusätzliche Informationen zur Fehlerbehebung.

Das Beispiel CustomFailureHandlerTest zeigt, wie eine benutzerdefinierte Fehler-Handler:

Kotlin

private class CustomFailureHandler(targetContext: Context) : FailureHandler {
    private val delegate: FailureHandler

    init {
        delegate = DefaultFailureHandler(targetContext)
    }

    override fun handle(error: Throwable, viewMatcher: Matcher<View>) {
        try {
            delegate.handle(error, viewMatcher)
        } catch (e: NoMatchingViewException) {
            throw MySpecialException(e)
        }

    }
}

Java

private static class CustomFailureHandler implements FailureHandler {
    private final FailureHandler delegate;

    public CustomFailureHandler(Context targetContext) {
        delegate = new DefaultFailureHandler(targetContext);
    }

    @Override
    public void handle(Throwable error, Matcher<View> viewMatcher) {
        try {
            delegate.handle(error, viewMatcher);
        } catch (NoMatchingViewException e) {
            throw new MySpecialException(e);
        }
    }
}

Dieser Fehler-Handler gibt MySpecialException anstelle eines NoMatchingViewException und delegiert alle anderen Fehler an die DefaultFailureHandler CustomFailureHandler kann registriert werden mit Espresso in der setUp()-Methode des Tests:

Kotlin

@Throws(Exception::class)
override fun setUp() {
    super.setUp()
    getActivity()
    setFailureHandler(CustomFailureHandler(
            ApplicationProvider.getApplicationContext<Context>()))
}

Java

@Override
public void setUp() throws Exception {
    super.setUp();
    getActivity();
    setFailureHandler(new CustomFailureHandler(
            ApplicationProvider.getApplicationContext()));
}

Weitere Informationen finden Sie in der FailureHandler und Espresso.setFailureHandler()

Targeting auf nicht standardmäßige Fenster

Android unterstützt mehrere Fenster. Normalerweise ist dies für die Nutzenden und dem App-Entwickler. In bestimmten Fällen sind jedoch mehrere Fenster sichtbar, z. B. z. B. wenn ein Fenster für die automatische Vervollständigung über dem Hauptfenster der das Such-Widget. Zur Vereinfachung verwendet Espresso standardmäßig eine Heuristik, raten, mit welchem Window du interagieren möchtest. Diese Heuristik ist fast immer gut genug; In seltenen Fällen müssen Sie jedoch angeben, auf die eine Interaktion ausgerichtet werden soll. Stellen Sie dazu Ihr eigenes Stammfenster bereit Matcher oder Root-Matcher:

Kotlin

onView(withText("South China Sea"))
    .inRoot(withDecorView(not(`is`(getActivity().getWindow().getDecorView()))))
    .perform(click())

Java

onView(withText("South China Sea"))
    .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))
    .perform(click());

Wie bei ViewMatchers, stellen wir eine Reihe von RootMatchers Natürlich können Sie auch jederzeit Ihr eigenes Matcher-Objekt implementieren.

Sehen Sie sich den Bericht MultipleWindowTest Beispiel auf GitHub.

Kopf- und Fußzeilen werden in ListViews mithilfe der Elemente addHeaderView() und addFooterView()-Methoden. Damit Espresso.onData() weiß, welches Datenobjekt verwenden Sie einen voreingestellten Datenobjektwert als zweiten Parameter, an addHeaderView() und addFooterView(). Beispiel:

Kotlin

const val FOOTER = "FOOTER"
...
val footerView = layoutInflater.inflate(R.layout.list_item, listView, false)
footerView.findViewById<TextView>(R.id.item_content).text = "count:"
footerView.findViewById<TextView>(R.id.item_size).text
        = data.size.toString
listView.addFooterView(footerView, FOOTER, true)

Java

public static final String FOOTER = "FOOTER";
...
View footerView = layoutInflater.inflate(R.layout.list_item, listView, false);
footerView.findViewById<TextView>(R.id.item_content).setText("count:");
footerView.findViewById<TextView>(R.id.item_size).setText(String.valueOf(data.size()));
listView.addFooterView(footerView, FOOTER, true);

Anschließend können Sie einen Matcher für die Fußzeile schreiben:

Kotlin

import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.instanceOf
import org.hamcrest.Matchers.`is`

fun isFooter(): Matcher<Any> {
    return allOf(`is`(instanceOf(String::class.java)),
            `is`(LongListActivity.FOOTER))
}

Java

import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;

@SuppressWarnings("unchecked")
public static Matcher<Object> isFooter() {
    return allOf(is(instanceOf(String.class)), is(LongListActivity.FOOTER));
}

Und das Laden der Ansicht in einem Test ist einfach:

Kotlin

import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.sample.LongListMatchers.isFooter

fun testClickFooter() {
    onData(isFooter())
        .perform(click())

    // ...
}

Java

import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.sample.LongListMatchers.isFooter;

public void testClickFooter() {
    onData(isFooter())
        .perform(click());

    // ...
}

Sehen Sie sich das vollständige Codebeispiel in der testClickFooter()-Methode von AdapterViewTest.java auf GitHub.