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)), hasEntry(equalTo("STR"),
            `is`("item: 50")))).perform(click())
    

자바

    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"))
    

자바

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

자바

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

자바

    public static Matcher<Object> withItemContent(String expectedText) {
        checkNotNull(expectedText);
        return withItemContent(equalTo(expectedText));
    }
    

이제 항목을 클릭하는 코드는 간단합니다.

Kotlin

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

자바

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

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

특정 하위 뷰 일치

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

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

다음과 같이 DataInteraction 구현에 onChildView() 사양을 추가하기만 하면 됩니다.

Kotlin

    onData(withItemContent("item: 60"))
        .onChildView(withId(R.id.item_size))
        .perform(click())
    

자바

    onData(withItemContent("item: 60"))
        .onChildView(withId(R.id.item_size))
        .perform(click());
    

Recycler 뷰 목록 항목과 상호작용

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

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

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

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

자바

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

자바

    @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() 진입점을 보여줍니다.