Espresso 목록

Espresso는 두 가지 유형의 목록(어댑터 뷰와 recycler 뷰)에서 특정 항목으로 스크롤하거나 특정 항목에서 작업을 실행하는 메커니즘을 제공합니다.

목록, 특히 RecyclerView 또는 AdapterView 객체로 만들어진 목록을 처리할 때 소수의 하위 요소만 표시되고 스크롤할 때 재활용되므로 관심 있는 뷰가 화면에 표시되지 않을 수도 있습니다. 이 경우에는 기존 뷰가 필요하므로 scrollTo() 메서드를 사용할 수 없습니다.

어댑터 뷰 목록 항목과 상호작용

onView() 메서드를 사용하는 대신 onData()로 검색을 시작하고 일치시키려는 뷰를 지원하는 데이터에 관한 매처를 제공합니다. Espresso가 Adapter 객체에서 행을 찾고 표시 영역에 항목을 표시하는 모든 작업을 실행합니다.

맞춤 뷰 매처를 사용하여 데이터 일치

아래 활동에는 Map<String, Object> 객체에 있는 각 행의 데이터를 보유하는 SimpleAdapter에서 지원하는 ListView가 포함되어 있습니다.

현재 화면에 표시된 목록 활동에는 23개의 항목이 있는 목록이 포함됩니다. 각 항목에는 문자열로 저장되고 다른 숫자에 매핑된 숫자가 있습니다. 이 숫자는 대신 객체로 저장됩니다.

각 맵에는 두 개의 항목이 있습니다. 문자열을 포함하는 "STR" 키(예: "item: x")와 콘텐츠의 길이를 나타내는 Integer를 포함하는 "LEN" 키입니다. 예:

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

'item: 50'이 있는 행을 클릭하기 위한 코드는 다음과 같습니다.

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

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" 값이 있는 항목이 포함된 모든 맵과 일치합니다. 이를 찾는 코드는 길고 다른 위치에서 이 코드를 재사용하려고 하므로 이를 위한 맞춤 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());

이 테스트의 전체 코드는 AdapterViewTest 클래스 내의 testClickOnItem50() 메서드와 GitHub의 이 맞춤 LongListMatchers 매처를 살펴보세요.

특정 하위 뷰 일치

위의 샘플은 ListView의 전체 행 중간에서 클릭을 발생시킵니다. 하지만 행의 특정 하위 요소에 관해 작업하려면 어떻게 해야 하나요? 예를 들어 LongListActivity 행의 두 번째 열을 클릭하려고 합니다. 이 열은 첫 번째 열에 콘텐츠의 문자열 길이를 표시합니다.

이 예에서는 특정 콘텐츠의 길이만 추출하는 것이 좋습니다. 이 프로세스에는 행의 두 번째 열 값을 결정하는 작업이 포함됩니다.

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

Recycler 뷰 목록 항목과 상호작용

RecyclerView 객체는 AdapterView 객체와 다르게 작동하므로 onData()를 사용하여 객체와 상호작용할 수 없습니다.

Espresso를 사용하여 RecyclerView와 상호작용하려면 espresso-contrib 패키지를 사용하면 됩니다. 이 패키지에는 위치로 스크롤하거나 항목에 관한 작업을 실행하는 데 사용할 수 있는 RecyclerViewActions 컬렉션이 있습니다.

  • scrollTo() - 일치하는 뷰가 있는 경우 그 뷰로 스크롤합니다.
  • scrollToHolder() - 일치하는 뷰 홀더가 있는 경우 이 뷰 홀더로 스크롤합니다.
  • scrollToPosition() - 특정 위치로 스크롤합니다.
  • actionOnHolderItem() - 일치하는 뷰 홀더에서 ViewAction을 실행합니다.
  • actionOnItem() - 일치하는 뷰에서 ViewAction을 실행합니다.
  • actionOnItemAtPosition() - 특정 위치의 뷰에서 ViewAction을 실행합니다.

다음 스니펫에는 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()));
}

추가 리소스

Android 테스트에서 Espresso 목록을 사용하는 방법에 관한 자세한 내용은 다음 리소스를 참고하세요.

샘플

  • DataAdapterSample: 목록 및 AdapterView 객체에 관한 Espresso의 onData() 진입점을 보여줍니다.