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:
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"))); }
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"))); }
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"))); }
Auf Geräten mit einer Schaltfläche für das Hardware-Dreipunkt-Menü sieht das so aus:
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"))); } }
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.
Eine Kopf- oder Fußzeile in einer Listenansicht angleichen
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.