Liste di caffè espresso

Espresso offre meccanismi per scorrere o agire su un determinato elemento per due tipi di elenchi: visualizzazioni adattatore e visualizzazioni riciclo.

Quando hai a che fare con elenchi, in particolare quelli creati con un oggetto RecyclerView o AdapterView, la visualizzazione che ti interessa potrebbe non apparire nemmeno sullo schermo perché solo un numero limitato di bambini viene mostrato e viene riciclato mentre scorri. Il metodo scrollTo() non può essere utilizzato in questo caso perché richiede una vista esistente.

Interagire con gli elementi dell'elenco della visualizzazione adattatore

Anziché utilizzare il metodo onView(), inizia la ricerca con onData() e fornisci un matcher con i dati a supporto della vista per cui vuoi trovare una corrispondenza. Espresso si occuperà di trovare la riga nell'oggetto Adapter e di rendere l'elemento visibile nell'area visibile.

Abbina i dati utilizzando un matcher visualizzazione personalizzata

L'attività seguente contiene un elemento ListView, supportato da un SimpleAdapter che contiene i dati di ogni riga in un oggetto Map<String, Object>.

L&#39;attività dell&#39;elenco attualmente mostrata sullo schermo contiene un elenco con 23 elementi. Ogni elemento ha un numero, archiviato come stringa, mappato a un numero diverso, che viene invece archiviato come oggetto.

Ogni mappa ha due voci: una chiave "STR" che contiene una stringa, ad esempio "item: x", e una chiave "LEN" che contiene un Integer, che rappresenta la lunghezza dei contenuti. Ecco alcuni esempi:

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

Il codice per un clic sulla riga contenente "elemento: 50" ha il seguente aspetto:

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

Tieni presente che Espresso scorre automaticamente l'elenco in base alle esigenze.

Smontiamo Matcher<Object> all'interno di onData(). Il metodo is(instanceOf(Map.class)) restringe la ricerca a qualsiasi elemento di AdapterView, che è supportato da un oggetto Map.

Nel nostro caso, questo aspetto della query corrisponde a ogni riga della visualizzazione elenco, ma vogliamo fare clic specificamente su un elemento, quindi restringiamo ulteriormente la ricerca con:

Kotlin

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

Java

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

Questo Matcher<String, Object> corrisponderà a qualsiasi mappa contenente una voce con la chiave "STR" e il valore "item: 50". Poiché il codice da cercare è lungo e vogliamo utilizzarlo in altre località, scriviamo un matcher withItemContent personalizzato:

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

Utilizzi un BoundedMatcher come base perché devi abbinare solo gli oggetti di tipo Map. Sostituisci il metodo matchesSafely(), inserendo il matcher trovato in precedenza e abbinalo a un Matcher<String> che puoi passare come argomento. In questo modo puoi chiamare withItemContent(equalTo("foo")). Per aumentare la visibilità del codice, puoi creare un altro matcher che chiami già equalTo() e accetti un oggetto 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));
}

Ora il codice per fare clic sull'elemento è semplice:

Kotlin

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

Java

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

Per il codice completo di questo test, dai un'occhiata al metodo testClickOnItem50() nella classe AdapterViewTest e a questo matcher LongListMatchers personalizzato su GitHub.

Abbina una vista secondaria specifica

L'esempio riportato sopra genera un clic al centro dell'intera riga di un elemento ListView. E se volessimo operare su un elemento secondario specifico della riga? Ad esempio, fai clic sulla seconda colonna della riga LongListActivity, che visualizza il valore String.length del contenuto nella prima colonna:

In questo esempio, sarebbe utile estrarre solo la lunghezza di un particolare contenuto. Questo processo comporta la determinazione del valore della seconda colonna di una riga.

È sufficiente aggiungere una specifica onChildView() all'implementazione di 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());

Interagire con gli elementi dell'elenco della visualizzazione dello strumento di riciclo

Gli oggetti RecyclerView funzionano in modo diverso rispetto agli oggetti AdapterView, pertanto onData() non può essere utilizzato per interagire con loro.

Per interagire con RecyclerViews utilizzando Espresso, puoi usare il pacchetto espresso-contrib, che contiene una raccolta di RecyclerViewActions che può essere utilizzata per scorrere fino alle posizioni o per eseguire azioni sugli elementi:

  • scrollTo(): scorre fino alla vista corrispondente, se esistente.
  • scrollToHolder(): scorre fino al proprietario della visualizzazione corrispondente, se esistente.
  • scrollToPosition(): scorre fino a una posizione specifica.
  • actionOnHolderItem(): esegue un'azione di visualizzazione su un blocco della visualizzazione corrispondente.
  • actionOnItem(): esegue un'azione di visualizzazione su una vista con corrispondenza.
  • actionOnItemAtPosition(): esegue un'azione ViewAction su una vista in una posizione specifica.

I seguenti snippet includono alcuni esempi tratte dall'esempio RecyclerViewSample:

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

Risorse aggiuntive

Per ulteriori informazioni sull'utilizzo delle liste Espresso nei test Android, consulta le seguenti risorse.

Samples

  • DataAdapterSample: mostra il punto di ingresso onData() per Espresso, per elenchi e AdapterView oggetti.