Espresso cung cấp các cơ chế để cuộn đến hoặc thao tác trên một mục cụ thể cho 2 loại danh sách: khung hiển thị trình chuyển đổi và khung hiển thị tái chế (recycler view).
Khi xử lý các danh sách, đặc biệt là những khung hiển thị được tạo bằng RecyclerView
hoặc đối tượng AdapterView
, khung hiển thị mà bạn quan tâm thậm chí có thể không xuất hiện trên màn hình vì chỉ có một số ít phần tử con xuất hiện và được tái chế khi bạn cuộn. Bạn không thể sử dụng phương thức scrollTo()
trong trường hợp này vì phương thức này yêu cầu khung hiển thị hiện có.
Tương tác với các mục trong danh sách khung hiển thị bộ chuyển đổi
Thay vì sử dụng phương thức onView()
, hãy bắt đầu tìm kiếm bằng onData()
và cung cấp trình so khớp dựa trên dữ liệu đang sao lưu khung hiển thị mà bạn muốn so khớp.
Espresso sẽ thực hiện mọi công việc để tìm hàng trong đối tượng Adapter
và hiển thị mục đó trong khung nhìn.
So khớp dữ liệu bằng trình so khớp khung hiển thị tuỳ chỉnh
Hoạt động bên dưới chứa ListView
, được hỗ trợ bởi SimpleAdapter
chứa dữ liệu cho từng hàng trong đối tượng Map<String, Object>
.
Mỗi bản đồ có hai mục nhập: một khoá "STR"
chứa Chuỗi (chẳng hạn như "item: x"
) và một khoá "LEN"
chứa Integer
(thể hiện độ dài của nội dung). Ví dụ:
{"STR" : "item: 0", "LEN": 7}
Mã cho một lượt nhấp vào hàng có "item: 50" sẽ có dạng như sau:
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());
Lưu ý: Espresso sẽ tự động cuộn qua danh sách khi cần.
Hãy tách Matcher<Object>
bên trong onData()
. Phương thức is(instanceOf(Map.class))
thu hẹp phạm vi tìm kiếm ở bất kỳ mục nào của AdapterView
, được hỗ trợ bởi một đối tượng Map
.
Trong trường hợp này, khía cạnh này của truy vấn khớp với mọi hàng của chế độ xem danh sách, nhưng chúng ta muốn nhấp cụ thể vào một mục, vì vậy chúng ta thu hẹp phạm vi tìm kiếm thêm bằng cách:
Kotlin
hasEntry(equalTo("STR"), `is`("item: 50"))
Java
hasEntry(equalTo("STR"), is("item: 50"))
Matcher<String, Object>
này sẽ khớp với mọi mục ánh xạ có chứa mục nhập với khoá "STR"
và giá trị "item: 50"
. Vì mã để tra cứu đoạn mã này mất nhiều thời gian và chúng ta muốn sử dụng lại ở các vị trí khác, hãy viết trình so khớp withItemContent
tuỳ chỉnh cho mã này:
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); } };
Bạn dùng BoundedMatcher
làm cơ sở vì để chỉ so khớp các đối tượng thuộc loại Map
. Ghi đè phương thức matchesSafely()
, đưa vào trình so khớp đã tìm thấy trước đó rồi so khớp phương thức đó với Matcher<String>
mà bạn có thể truyền dưới dạng đối số. Điều này cho phép bạn gọi cho withItemContent(equalTo("foo"))
. Để rút ngắn mã, bạn có thể tạo một trình so khớp khác đã gọi equalTo()
và chấp nhận đối tượng 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)); }
Giờ đây, mã để nhấp vào mục này trở nên đơn giản:
Kotlin
onData(withItemContent("item: 50")).perform(click())
Java
onData(withItemContent("item: 50")).perform(click());
Để biết mã đầy đủ của bài kiểm thử này, hãy xem phương thức testClickOnItem50()
trong lớp AdapterViewTest
và trình so khớp LongListMatchers
tuỳ chỉnh này trên GitHub.
Khớp với một chế độ xem con cụ thể
Mẫu ở trên đưa ra một lượt nhấp vào giữa toàn bộ hàng của ListView
.
Nhưng nếu chúng ta muốn thao tác trên một phần tử con cụ thể của hàng thì sao? Ví dụ: chúng ta muốn nhấp vào cột thứ hai trong hàng của LongListActivity
, cột này hiển thị String.length của nội dung trong cột đầu tiên:
Bạn chỉ cần thêm thông số kỹ thuật onChildView()
vào quá trình triển khai 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());
Tương tác với các mục trong danh sách thành phần hiển thị tái chế
Đối tượng RecyclerView
hoạt động khác với đối tượng AdapterView
nên bạn không thể dùng onData()
để tương tác với đối tượng đó.
Để tương tác với RecyclerViews bằng Espresso, bạn có thể dùng gói
espresso-contrib
. Gói này có một tập hợp
RecyclerViewActions
có thể dùng để cuộn đến các vị trí hoặc thực hiện thao tác trên các mục:
scrollTo()
– Cuộn đến Chế độ xem phù hợp, nếu có.scrollToHolder()
– Cuộn đến Trình giữ khung hiển thị trùng khớp, nếu có.scrollToPosition()
– Cuộn đến một vị trí cụ thể.actionOnHolderItem()
– Thực hiện Hành động đối với khung hiển thị trên một Chủ sở hữu khung hiển thị trùng khớp.actionOnItem()
– Thực hiện Hành động đối với thành phần hiển thị trên một Thành phần hiển thị trùng khớp.actionOnItemAtPosition()
– Thực hiện một ViewAction trên khung hiển thị tại một vị trí cụ thể.
Các đoạn mã sau đây nêu bật một số ví dụ từ mẫu 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())); }
Tài nguyên khác
Để biết thêm thông tin về cách sử dụng danh sách Espresso trong kiểm thử Android, hãy tham khảo các tài nguyên sau.
Mẫu
- DataAdapterSample: Cho thấy điểm truy cập
onData()
cho Espresso, cho các danh sách và đối tượngAdapterView
.