如何在Espresso中的RecyclerView内断言?

时间:2015-07-13 22:27:50

标签: java android android-espresso

我正在使用espresso-contrib来对RecyclerView执行操作,它可以正常工作,例如:

onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions.actionOnItemAtPosition(0, click())); //click on first item

我需要对它执行断言。像这样:

onView(withId(R.id.recycler_view))
    .perform(RecyclerViewActions.actionOnItemAtPosition(0, check(matches(withText("Test Text"))));

但是,因为RecyclerViewActions当然是期待一个动作,所以它说错误的第二个参数类型。 espresso-contrib上没有RecyclerViewAssertions

有没有办法在回收站视图上执行断言?

12 个答案:

答案 0 :(得分:73)

非常简单。不需要额外的库。做:

    onView(withId(R.id.recycler_view))
            .check(matches(atPosition(0, withText("Test Text"))));

如果您的ViewHolder使用ViewGroup,请使用withText()包裹hasDescendant(),如:

onView(withId(R.id.recycler_view))
                .check(matches(atPosition(0, hasDescendant(withText("Test Text")))));

使用您可以放入Utils课程的方法。

public static Matcher<View> atPosition(final int position, @NonNull final Matcher<View> itemMatcher) {
    checkNotNull(itemMatcher);
    return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
        @Override
        public void describeTo(Description description) {
            description.appendText("has item at position " + position + ": ");
            itemMatcher.describeTo(description);
        }

        @Override
        protected boolean matchesSafely(final RecyclerView view) {
            RecyclerView.ViewHolder viewHolder = view.findViewHolderForAdapterPosition(position);
            if (viewHolder == null) {
                // has no item on such position
                return false;
            }
            return itemMatcher.matches(viewHolder.itemView);
        }
    };
}

如果您的项目最初可能在屏幕上不可见,请在之前滚动到该项目:

    onView(withId(R.id.recycler_view))
            .perform(scrollToPosition(87))
            .check(matches(atPosition(87, withText("Test Text"))));

答案 1 :(得分:45)

你应该查看Danny Roa的解决方案Custom RecyclerView Actions 并像这样使用它:

onView(withRecyclerView(R.id.recycler_view)
    .atPositionOnView(1, R.id.ofElementYouWantToCheck))
    .check(matches(withText("Test text")));

答案 2 :(得分:23)

只是为了增强riwnodennyk的答案,如果您的<div data-bind="foreach : info"> <p data-bind="$data.cash"></p> <p data-bind="$data.orig_id"></p> <input type="checkbox" class="mySwitch" data-bind="bootstrapSwitchOn : $data,callFunction:$root.clickHandler"/> </div> ViewHolder而不是像ViewGroup这样的直接View对象,那么您可以添加TextView匹配器匹配hasDescendant中的TextView对象。例如:

ViewGroup

答案 3 :(得分:4)

我一直在努力解决同样的问题,我有6个项目的回收者视图然后我必须选择位置5的那个,并且第5个在视图上不可见。
上述解决方案也有效。此外,我知道它有点晚了,但认为值得分享。

我用一个简单的解决方案做到了这一点:

onView(withId(R.id.recylcer_view))
    .perform(RecyclerViewActions.actionOnItem(
         hasDescendant(withText("Text of item you want to scroll to")), 
         click()));

RecyclerViewActions.actionOnItem - 在与ViewAction匹配的视图上执行viewHolderMatcher

  1. RecyclerView滚动到与itemViewMatcher
  2. 匹配的视图
  3. 对匹配的视图执行操作
  4. 更多详细信息,请访问:RecyclerViewActions.actionOnItem

答案 4 :(得分:3)

就我而言,有必要合并Danny Roa&riwnodennyk的解决方案:

onView(withId(R.id.recyclerview))
.perform(RecyclerViewActions.scrollToPosition(80))
.check(matches(atPositionOnView(80, withText("Test Test"), R.id.targetview)));

和方法:

public static Matcher<View> atPositionOnView(final int position, final Matcher<View> itemMatcher,
        @NonNull final int targetViewId) {

    return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
        @Override
        public void describeTo(Description description) {
            description.appendText("has view id " + itemMatcher + " at position " + position);
        }

        @Override
        public boolean matchesSafely(final RecyclerView recyclerView) {
            RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position);
            View targetView = viewHolder.itemView.findViewById(targetViewId);
            return itemMatcher.matches(targetView);
        }
    };
}

答案 5 :(得分:2)

上面的解决方案很棒,我将在Kotlin中再贡献一个。

Espresso需要滚动到该位置,然后进行检查。要滚动到该位置,我正在使用RecyclerViewActions.scrollToPosition。然后使用findViewHolderForAdapterPosition进行检查。而不是直接调用findViewHolderForAdapterPosition,而是使用更好的便捷功能childOfViewAtPositionWithMatcher

在下面的示例代码中,Espresso滚动到RecyclerView的第43行,并检查该行的ViewHolder有两个TextView,并检查这些TextView包含文本“ hello text 1 at line 43”和“您好文本2,第43行”:

import androidx.test.espresso.contrib.RecyclerViewActions
import it.xabaras.android.espresso.recyclerviewchildactions.RecyclerViewChildActions.Companion.childOfViewAtPositionWithMatcher

// ...

        val index = 42 // index is 0 based, corresponds to row 43 from user's perspective

        onView(withId(R.id.myRecyclerView))
            .perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(43))
            .check(
                matches(
                    allOf(
                        childOfViewAtPositionWithMatcher(
                            R.id.myTextView1,
                            index,
                            withText("hello text 1 at line 43")
                        ),
                        childOfViewAtPositionWithMatcherMy(
                            R.id.myTextView2,
                            index,
                            withText("hello text 2 at line 43")
                        )
                    )
                )
            )

这是我的build.gradle依赖项(可能存在更新的版本):

androidTestImplementation "androidx.test.espresso:espresso-contrib:3.2.0"
androidTestImplementation "it.xabaras.android.espresso:recyclerview-child-actions:1.0"

答案 6 :(得分:1)

Danny Roa的解决方案很棒,但它对我刚刚滚动到屏幕外的项目不起作用。修复正在取代这个:

View targetView = recyclerView.getChildAt(this.position)
                              .findViewById(this.viewId);

用这个:

View targetView = recyclerView.findViewHolderForAdapterPosition(this.position)
                              .itemView.findViewById(this.viewId);

...在enter image description here

答案 7 :(得分:1)

该线程非常老,但是我最近扩展了RecyclerViewAction支持,使其也可以用于断言。这样,您就可以在仍然使用视图匹配器的情况下测试屏幕外元素,而无需指定位置。

查看本文> Better Android RecyclerView Testing

答案 8 :(得分:1)

要测试所有项目(包括屏幕外),请使用下面的代码

示例:

(k_1 + 1) * ... * (k_t + 1)

班级:

fun checkThatRedDotIsGone(itemPosition: Int) {
    onView(
            withId(R.id.recycler_view)).
            check(itemViewMatches(itemPosition, R.id.inside_item_view,
                    withEffectiveVisibility(Visibility.GONE)))
}

}

基于本文的解决方案> Better Android RecyclerView Testing!

答案 9 :(得分:0)

随着项目的发展,我将上面的一些答案组合成一种可重用的方法,用于测试其他回收者视图。如果将代码留在这里,可以帮助其他人...

声明一个可以在RecyclerView资源ID上调用的函数:

fun Int.shouldHaveTextAtPosition(text:String, position: Int) {
    onView(withId(this))
        .perform(scrollToPosition<RecyclerView.ViewHolder>(position))
        .check(matches(atPosition(position, hasDescendant(withText(text)))))
}

然后在回收者视图中调用它,以执行以下操作以检查列表中给定位置的多个适配器项目的文本显示:

with(R.id.recycler_view_id) {
    shouldHaveTextAtPosition("Some Text at position 0", 0)
    shouldHaveTextAtPosition("Some Text at position 1", 1)
    shouldHaveTextAtPosition("Some Text at position 2", 2)
    shouldHaveTextAtPosition("Some Text at position 3", 3)
}

答案 10 :(得分:0)

修改后的匹配器解决方案,用于检查项目(TextView中的Text)是否在RecyclerView中位于第一个位置。

// True if first item in the RV and input text
fun <T> customMatcherForFirstItem(name: String, matcher: Matcher<T>): Matcher<T> {
    return object: BaseMatcher<T> () {
        var isFirst = true
        override fun matches(item: Any): Boolean {
            if (isFirst && (item as TextView).text == name) {
                isFirst = false
                return true
            }
            return false
        }
        override fun describeTo(description: Description?) {}
    }
}

用法:(如果该项目中的子textview具有与我们期望/搜索相同的文本,则返回true

    onView(withText("TEXT_VIEW_TEXT")).check(matches(
        customMatcherForFirstItem("TEXT_VIEW_TEXT", withParent(withId(R.id.recycler_view)))))

基于-> https://proandroiddev.com/working-with-recycler-views-in-espresso-tests-6da21495182c

答案 11 :(得分:0)

对于 Kotlin 用户: 我知道我迟到了,我很困惑为什么人们必须自定义 RecycleView Actions。我一直想达到完全相同的预期结果:这是我的解决方案,希望它有效并帮助新人。也请对我放轻松,因为我可能会被阻止。

onView(withId(R.id.recycler_view))
 .perform(actionOnItemAtPosition<RecyclerView.ViewHolder>(46, scrollTo()))
       .check(matches(hasDescendant(withText("TextToMatch"))))