Listes d'Espresso

Espresso propose des mécanismes permettant de faire défiler ou d'agir sur un élément particulier pour deux types de listes: les vues d'adaptateur et les vues recycler.

Lorsque vous traitez des listes, en particulier celles créées avec un objet RecyclerView ou AdapterView, il est possible que la vue qui vous intéresse ne s'affiche même pas à l'écran, car seul un petit nombre d'enfants sont affichés et recyclés à mesure que vous faites défiler l'écran. Dans ce cas, la méthode scrollTo() ne peut pas être utilisée, car elle nécessite une vue existante.

Interagir avec les éléments de la liste de la vue de l'adaptateur

Au lieu d'utiliser la méthode onView(), commencez votre recherche avec onData() et fournissez un outil de mise en correspondance avec les données qui sous-tendent la vue que vous souhaitez mettre en correspondance. Espresso effectue tout le travail : trouver la ligne dans l'objet Adapter et rendre l'élément visible dans la fenêtre d'affichage.

Faire correspondre des données à l'aide d'un outil de mise en correspondance des vues personnalisé

L'activité ci-dessous contient un ListView, reposant sur un SimpleAdapter contenant les données pour chaque ligne d'un objet Map<String, Object>.

L&#39;activité de liste actuellement affichée à l&#39;écran contient une liste de 23 éléments. Chaque élément possède un nombre, stocké en tant que chaîne, mappé à un nombre différent, qui est stocké en tant qu&#39;objet.

Chaque mappage comporte deux entrées: une clé "STR" contenant une chaîne, telle que "item: x", et une clé "LEN" contenant un Integer, qui représente la longueur du contenu. Par exemple :

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

Le code d'un clic sur la ligne contenant "item: 50" se présente comme suit:

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

Notez qu'Espresso fait défiler la liste automatiquement si nécessaire.

Démontons maintenant le Matcher<Object> dans onData(). La méthode is(instanceOf(Map.class)) limite la recherche à n'importe quel élément de AdapterView, qui repose sur un objet Map.

Dans notre cas, cet aspect de la requête correspond à chaque ligne de la liste, mais nous voulons cliquer spécifiquement sur un élément. Nous allons donc affiner la recherche avec:

Kotlin

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

Java

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

Ce Matcher<String, Object> correspond à tout Map contenant une entrée avec la clé "STR" et la valeur "item: 50". Étant donné que le code permettant d'effectuer cette recherche est long et que nous souhaitons le réutiliser à d'autres emplacements, nous allons créer un outil de mise en correspondance withItemContent personnalisé pour cela:

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

Vous utilisez un BoundedMatcher comme base, car il ne doit correspondre qu'aux objets de type Map. Remplacez la méthode matchesSafely() en plaçant l'outil de mise en correspondance trouvé précédemment, puis comparez-le à un Matcher<String> que vous pouvez transmettre en tant qu'argument. Cela vous permet d'appeler withItemContent(equalTo("foo")). Par souci de concision du code, vous pouvez créer un autre outil de mise en correspondance qui appelle déjà equalTo() et accepte un objet 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));
}

À présent, le code permettant de cliquer sur l'élément est simple:

Kotlin

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

Java

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

Pour obtenir le code complet de ce test, consultez la méthode testClickOnItem50() de la classe AdapterViewTest et cet outil de mise en correspondance LongListMatchers personnalisé sur GitHub.

Faire correspondre à une vue enfant spécifique

L'exemple ci-dessus génère un clic au milieu de la ligne entière d'un élément ListView. Mais que se passe-t-il si nous voulons opérer sur un enfant spécifique de la ligne ? Par exemple, nous souhaitons cliquer sur la deuxième colonne de la ligne de LongListActivity pour afficher la valeur "String.length" du contenu dans la première colonne:

Dans cet exemple, il serait judicieux d&#39;extraire uniquement la longueur d&#39;un contenu particulier. Ce processus implique de déterminer la valeur de la deuxième colonne d&#39;une ligne.

Il vous suffit d'ajouter une spécification onChildView() à votre implémentation de 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());

Interagir avec les éléments de la liste "RecyclerView"

Les objets RecyclerView fonctionnent différemment des objets AdapterView. Par conséquent, onData() ne peut pas être utilisé pour interagir avec eux.

Pour interagir avec les RecyclerViews à l'aide d'Espresso, vous pouvez utiliser le package espresso-contrib, qui contient un ensemble de RecyclerViewActions permettant de faire défiler des éléments jusqu'à certaines positions ou d'effectuer des actions sur des éléments:

  • scrollTo() : fait défiler l'écran jusqu'à la vue correspondante, le cas échéant.
  • scrollToHolder() : fait défiler la page jusqu'au titulaire de la vue correspondant, le cas échéant.
  • scrollToPosition() : fait défiler l'écran jusqu'à une position spécifique.
  • actionOnHolderItem() : effectue une action d'affichage sur un conteneur de vue correspondant.
  • actionOnItem() : effectue une action d'affichage sur une vue correspondante.
  • actionOnItemAtPosition() : effectue une ViewAction sur une vue à une position spécifique.

Les extraits de code suivants présentent quelques exemples issus de l'exemple 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()));
}

Ressources supplémentaires

Pour en savoir plus sur l'utilisation des listes Espresso dans les tests Android, consultez les ressources suivantes.

Exemples

  • DataAdapterSample : présente le point d'entrée onData() pour Espresso, pour les listes et les objets AdapterView.