Espresso リスト

Espresso には、アダプター ビューとリサイクラー ビューの 2 種類のリストについて、特定の項目までスクロールしたり、特定の項目を操作したりするメカニズムが用意されています。

リスト(特に、RecyclerView または AdapterView オブジェクトで作成されたリスト)の処理では、スクロールによって少数の子要素のみが表示およびリサイクルされ、関心のあるビューが画面上に表示されない可能性があります。この場合、既存のビューを必要とする scrollTo() メソッドは使用できません。

アダプター ビューのリスト項目の操作

onView() メソッドの代わりに onData() を使って検索を開始し、マッチングするビューの背後にあるデータに対するマッチャーを用意します。Adapter オブジェクト内で行を見つけてその項目をビューポートに表示するための操作は、すべて Espresso により行われます。

カスタムビュー マッチャーを使用したデータのマッチング

下記のアクティビティには、Map<String, Object> オブジェクトの各行のデータを保持する SimpleAdapter が設定された ListView が含まれています。

画面上のリスト アクティビティには 23 項目のリストが示されています。各項目には文字列として保存される番号が付いています。その番号は、オブジェクトとして保存される別の番号にマッピングされています。

各 Map には 2 つのエントリがあります。1 つはキー "STR" で、"item: x" のような文字列を含みます。もう 1 つはキー "LEN" で、コンテンツの長さを表す Integer を含みます。例:

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

「item: 50」の行をクリックするコードは次のようになります。

Kotlin

    onData(allOf(`is`(instanceOf(Map.class)), hasEntry(equalTo("STR"),
            `is`("item: 50")))).perform(click())
    

Java

    onData(allOf(is(instanceOf(Map.class)), hasEntry(equalTo("STR"), is("item: 50"))))
        .perform(click());
    

なお、Espresso によりリストは必要に応じて自動的にスクロールされます。

onData() 内の Matcher<Object> を細かく見てみましょう。is(instanceOf(Map.class)) メソッドにより、検索対象が AdapterViewMap オブジェクトが背後にある)の任意の項目に絞り込まれます。

ここまでのクエリではリストビューのすべての行が一致しますが、今回は特定の項目をクリックする必要があるため、次のようにして検索対象をさらに絞り込みます。

Kotlin

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

Java

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

この Matcher<String, Object> により、キーが "STR" で値が "item: 50" のエントリを含むすべての Map がマッチングされます。この検索コードは長いため、他の場所でも再利用できるように、次のようにしてカスタム withItemContent マッチャーを作成します。

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

Map 型のオブジェクトとのみマッチングするため、BoundedMatcher をベースとして使用します。matchesSafely() メソッドをオーバーライドし、上で特定したマッチャーを入れて、引数で渡せる Matcher<String> とマッチングします。これにより、withItemContent(equalTo("foo")) を呼び出せるようになります。コードを簡潔にするために、次のようにして、あらかじめ equalTo() を呼び出して 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));
    }
    

これで、項目をクリックするためのコードは次のように簡単になります。

Kotlin

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

Java

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

このテストのコード全体については、GitHub の AdapterViewTest クラス内の testClickOnItem50() メソッドと、このカスタム LongListMatchers マッチャーをご覧ください。

特定の子ビューのマッチング

上記の例では、ListView の該当行の中央でクリックが行われます。今度は、行の特定の子要素を操作する場合を考えます。たとえば、次のように、LongListActivity の行の 2 番目の列(最初の列のコンテンツの文字列長を示す)をクリックすることにします。

この例では、便宜上コンテンツの一部の長さだけを抽出しています。このプロセスでは、行の 2 番目の列の値を決定します。

これは、次のように DataInteraction の実装に onChildView() の指定を追加するだけで済みます。

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

リサイクラー ビューのリスト項目の操作

RecyclerView オブジェクトは AdapterView オブジェクトと動作が異なり、onData() を使用して操作することはできません。

Espresso では、espresso-contrib パッケージを使用して RecyclerViews を操作できます。このパッケージには、項目の位置までスクロールしたり、項目に対してアクションを行ったりするための、以下のような RecyclerViewActions コレクションが含まれています。

  • scrollTo() - 一致するビューまでスクロールします。
  • scrollToHolder() - 一致するビューホルダーまでスクロールします。
  • scrollToPosition() - 特定の位置までスクロールします。
  • actionOnHolderItem() - 一致するビューホルダーでビュー アクションを行います。
  • actionOnItem() - 一致するビューでビュー アクションを行います。
  • actionOnItemAtPosition() - 特定の位置のビューでビュー アクションを行います。

以下のスニペットは、RecyclerViewSample サンプルの使用例です。

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

参考情報

Espresso リストを使用した Android のテストに関するその他の情報については、次のリソースをご覧ください。

サンプル

  • DataAdapterSample: Espresso でリストや AdapterView オブジェクトを操作するための onData() エントリ ポイントを示します。