Espresso には、アダプター ビューとリサイクラー ビューの 2 種類のリストについて、特定の項目までスクロールしたり、特定の項目を操作したりするメカニズムが用意されています。
リスト(特に、RecyclerView
または AdapterView
オブジェクトで作成されたリスト)の処理では、スクロールによって少数の子要素のみが表示およびリサイクルされるため、表示したいビューが画面上に表示されないことがあります。この場合、既存のビューを必要とする scrollTo()
メソッドは使用できません。
アダプター ビューのリスト項目の操作
onView()
メソッドの代わりに onData()
を使用して検索を開始し、マッチングするビューの背後にあるデータに対するマッチャーを提供します。Adapter
オブジェクト内で行を見つけてその項目をビューポートに表示する処理は、すべて Espresso により行われます。
カスタムビュー マッチャーを使用したデータのマッチング
以下のアクティビティには、Map<String, Object>
オブジェクトの各行のデータを保持する SimpleAdapter
を背後に持つ ListView
が含まれています。
各マップには 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))
メソッドは、Map
オブジェクトを背後に持つ AdapterView
の任意の項目に検索対象を絞り込みます。
この例のクエリはリストビューのすべての行に一致しますが、今回は特定の項目をクリックするため、次のようにして検索対象をさらに絞り込みます。
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 番目の列(最初の列のコンテンツの文字列長を示します)をクリックしたいとします。
これは、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
パッケージを使用して RecyclerView を操作できます。このパッケージには、項目の位置までのスクロールまたは項目に対するアクションを行うことができる 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()
エントリ ポイントを紹介しています。