Espresso предлагает механизмы прокрутки к определенному элементу или действия с ним для двух типов списков: представления адаптера и представления переработчика.
При работе со списками, особенно созданными с помощью объекта RecyclerView
или AdapterView
, интересующее вас представление может даже не отображаться на экране, поскольку отображается лишь небольшое количество дочерних элементов, которые перезапускаются при прокрутке. В этом случае метод scrollTo()
использовать нельзя, поскольку для него требуется существующее представление.
Взаимодействие с элементами списка просмотра адаптера
Вместо использования метода onView()
начните поиск с onData()
и предоставьте средство сопоставления с данными, которые поддерживают представление, которое вы хотите сопоставить. Espresso выполнит всю работу по поиску строки в объекте Adapter
и сделает элемент видимым в области просмотра.
Сопоставление данных с помощью специального средства сопоставления представлений
Приведенное ниже действие содержит ListView
, поддерживаемый SimpleAdapter
, который содержит данные для каждой строки в объекте Map<String, Object>
.
Каждая карта имеет две записи: ключ "STR"
, который содержит строку, например "item: x"
, и ключ "LEN"
, который содержит Integer
, которое представляет длину содержимого. Например:
{"STR" : "item: 0", "LEN": 7}
Код клика по строке с «item: 50» выглядит следующим образом:
Котлин
onData(allOf(`is`(instanceOf(Map::class.java)), hasEntry(equalTo("STR"), `is`("item: 50")))).perform(click())
Ява
onData(allOf(is(instanceOf(Map.class)), hasEntry(equalTo("STR"), is("item: 50")))) .perform(click());
Обратите внимание, что Espresso автоматически прокручивает список по мере необходимости.
Давайте разберем Matcher<Object>
внутри onData()
. Метод is(instanceOf(Map.class))
сужает поиск до любого элемента AdapterView
, который поддерживается объектом Map
.
В нашем случае этот аспект запроса соответствует каждой строке представления списка, но мы хотим щелкнуть конкретно по элементу, поэтому мы еще больше сужаем поиск с помощью:
Котлин
hasEntry(equalTo("STR"), `is`("item: 50"))
Ява
hasEntry(equalTo("STR"), is("item: 50"))
Этот Matcher<String, Object>
будет соответствовать любой карте, содержащей запись с ключом "STR"
и значением "item: 50"
. Поскольку код для поиска длинный и мы хотим повторно использовать его в других местах, давайте напишем для этого собственный withItemContent
:
Котлин
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) } }
Ява
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); } };
Вы используете BoundedMatcher
в качестве основы, потому что сопоставляется только с объектами типа Map
. Переопределите метод matchesSafely()
, вставив найденное ранее средство сопоставления и сопоставив его с Matcher<String>
, который можно передать в качестве аргумента. Это позволяет вам вызывать withItemContent(equalTo("foo"))
. Для краткости кода вы можете создать еще одно средство сопоставления, которое уже вызывает equalTo()
и принимает объект String
:
Котлин
fun withItemContent(expectedText: String): Matcher<Object> { checkNotNull(expectedText) return withItemContent(equalTo(expectedText)) }
Ява
public static Matcher<Object> withItemContent(String expectedText) { checkNotNull(expectedText); return withItemContent(equalTo(expectedText)); }
Теперь код для нажатия на элемент прост:
Котлин
onData(withItemContent("item: 50")).perform(click())
Ява
onData(withItemContent("item: 50")).perform(click());
Полный код этого теста см. в методе testClickOnItem50()
в классе AdapterViewTest
и в этом специальном сопоставителе LongListMatchers
на GitHub.
Соответствие определенному дочернему представлению
В приведенном выше примере выдается щелчок в середине всей строки ListView
. Но что, если мы хотим обработать конкретный дочерний элемент строки? Например, мы хотели бы щелкнуть второй столбец строки LongListActivity
, который отобразит String.length содержимого в первом столбце:
Просто добавьте спецификацию onChildView()
в вашу реализацию DataInteraction
:
Котлин
onData(withItemContent("item: 60")) .onChildView(withId(R.id.item_size)) .perform(click())
Ява
onData(withItemContent("item: 60")) .onChildView(withId(R.id.item_size)) .perform(click());
Взаимодействие с элементами списка просмотра переработчика
Объекты RecyclerView
работают иначе, чем объекты AdapterView
, поэтому onData()
нельзя использовать для взаимодействия с ними.
Чтобы взаимодействовать с RecyclerViews с помощью Espresso, вы можете использовать пакет espresso-contrib
, в котором есть коллекция RecyclerViewActions
, которую можно использовать для прокрутки к позициям или выполнения действий над элементами:
-
scrollTo()
— прокручивает до соответствующего представления, если оно существует. -
scrollToHolder()
— прокручивает до соответствующего держателя представления, если он существует. -
scrollToPosition()
— Прокручивает до определенной позиции. -
actionOnHolderItem()
— выполняет действие просмотра для соответствующего держателя представления. -
actionOnItem()
— выполняет действие просмотра для соответствующего представления. -
actionOnItemAtPosition()
— выполняет ViewAction для представления в определенной позиции.
В следующих фрагментах представлены некоторые примеры из образца RecyclerViewSample :
Котлин
@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")) ) ) }
Ява
@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")) )); }
Котлин
@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())) }
Ява
@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())); }
Котлин
@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())) }
Ява
@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 : демонстрирует точку входа
onData()
для Espresso, для списков и объектовAdapterView
.