Java 如何在浓缩咖啡中的RecyclerView中进行断言?

Java 如何在浓缩咖啡中的RecyclerView中进行断言?,java,android,android-espresso,Java,Android,Android Espresso,我正在使用espresso contrib在RecyclerView上执行操作,它可以正常工作,例如: //单击第一项 onView(带id(R.id.recycler\U视图)) .perform(RecycleServiceActions.ActionItemPosition(0,单击()); 我需要对它执行断言。大概是这样的: onView(withId(R.id.recycler_view)) .perform( RecyclerViewActions.acti

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

//单击第一项
onView(带id(R.id.recycler\U视图))
.perform(RecycleServiceActions.ActionItemPosition(0,单击());
我需要对它执行断言。大概是这样的:

onView(withId(R.id.recycler_view))
    .perform(
        RecyclerViewActions.actionOnItemAtPosition(
            0, check(matches(withText("Test Text")))
        )
    );
但是,因为,当然,它期望一个动作,所以它表示错误的第二个参数类型。浓缩咖啡contrib上没有
RecycleServiceAssertions


是否有任何方法可以在
RecyclerView
上执行断言?

您应该查看Danny Roa的解决方案 然后像这样使用它:

onView(withId(R.id.recycler_view))
    .perform(
        RecyclerViewActions.actionOnItemAtPosition(
            0, check(matches(withText("Test Text")))
        )
    );
onView(带回收器视图(R.id.recycler\u视图)
.AtPositionView(1,您要检查的元件的R.id))
.检查(匹配(与文本(“测试文本”));

非常简单。不需要额外的图书馆。做:

    onView(withId(R.id.recycler_view))
            .check(matches(atPosition(0, withText("Test Text"))));
如果您的ViewHolder使用ViewGroup,请将
withText()
hasGenerant()换行,如:

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

为了增强riwnodennyk的回答,如果您的
视图持有者是
视图组
,而不是像
TextView
这样的直接
视图
对象,那么您可以添加
hasgendant
匹配器来匹配
视图组中的
TextView
对象。例如:

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

Danny Roa的解决方案很棒,但对于我刚刚滚动到的屏幕外项目,它不起作用。修复程序正在替换此:

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

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

。。。在我的案例中,有必要合并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);
        }
    };
}
公共静态匹配器AtPositionView(最终int位置,最终匹配器itemMatcher,
@非空最终int targetViewId){
返回新的BoundedMatcher(RecyclerView.class){
@凌驾
公共无效说明(说明){
description.appendText(“具有视图id”+项目匹配器+”位于“+位置);
}
@凌驾
公共布尔匹配安全(最终循环视图循环视图){
RecyclerView.ViewHolder ViewHolder=RecyclerView.FindViewHolder for AdapterPosition(位置);
View targetView=viewHolder.itemView.findViewById(targetViewId);
返回itemMatcher.matches(targetView);
}
};
}

我一直在努力解决同一个问题,我有一个包含6个项目的回收器视图,然后我必须选择位于位置5的一个,而第5个项目在视图中不可见。
上述解决方案也有效。另外,我知道有点晚了,但觉得值得分享

我用一个简单的解决方案做到了这一点,如下所示:

onView(withId(R.id.recylcer_view))
    .perform(RecyclerViewActions.actionOnItem(
         hasDescendant(withText("Text of item you want to scroll to")), 
         click()));
RecycleServiceActions.ActionItem
- 对与
viewHolderMatcher
匹配的视图执行
ViewAction

  • 滚动
    RecyclerView
    itemViewMatcher匹配的视图
  • 对匹配的视图执行操作

  • 更多详细信息请访问:

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

    查看这篇文章>

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

    样本:

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

    object RecyclerViewAssertions {
    
    fun itemViewMatches(position: Int, viewMatcher: Matcher<View>): ViewAssertion {
        return itemViewMatches(position, -1, viewMatcher)
    }
    
    /**
     * Provides a RecyclerView assertion based on a view matcher. This allows you to
     * validate whether a RecyclerView contains a row in memory without scrolling the list.
     *
     * @param viewMatcher - an Espresso ViewMatcher for a descendant of any row in the recycler.
     * @return an Espresso ViewAssertion to check against a RecyclerView.
     */
    fun itemViewMatches(position: Int, @IdRes resId: Int, viewMatcher: Matcher<View>): ViewAssertion {
        assertNotNull(viewMatcher)
    
        return ViewAssertion { view, noViewException ->
            if (noViewException != null) {
                throw noViewException
            }
    
            assertTrue("View is RecyclerView", view is RecyclerView)
    
            val recyclerView = view as RecyclerView
            val adapter = recyclerView.adapter
            val itemType = adapter!!.getItemViewType(position)
            val viewHolder = adapter.createViewHolder(recyclerView, itemType)
            adapter.bindViewHolder(viewHolder, position)
    
            val targetView = if (resId == -1) {
                viewHolder.itemView
            } else {
                viewHolder.itemView.findViewById(resId)
            }
    
            if (viewMatcher.matches(targetView)) {
                return@ViewAssertion  // Found a matching view
            }
    
            fail("No match found")
        }
    }
    
    对象回收服务组件{
    有趣的itemViewMatches(位置:Int,viewMatcher:Matcher):ViewAssertion{
    return itemViewMatches(位置-1,viewMatcher)
    }
    /**
    *提供基于视图匹配器的RecyclerView断言。这允许您
    *在不滚动列表的情况下验证RecyclerView是否在内存中包含行。
    *
    *@param viewMatcher-回收器中任何行的后代的浓缩咖啡viewMatcher。
    *@返回Espresso ViewAssertion以对照RecyclerView进行检查。
    */
    有趣的itemViewMatches(位置:Int,@idresid:Int,viewMatcher:Matcher):ViewAssertion{
    assertNotNull(视图匹配器)
    返回ViewAssertion{view,NovieweException->
    if(noViewException!=null){
    抛出新的例外
    }
    assertTrue(“视图为RecyclerView”,视图为RecyclerView)
    val recyclerView=查看为recyclerView
    val adapter=recyclerView.adapter
    val itemType=适配器!!.getItemViewType(位置)
    val viewHolder=adapter.createViewHolder(recyclerView,itemType)
    适配器。bindViewHolder(viewHolder,位置)
    val targetView=if(剩余=1){
    viewHolder.itemView
    }否则{
    viewHolder.itemView.findViewById(剩余)
    }
    if(viewMatcher.matches(targetView)){
    return@ViewAssertion//找到了一个匹配的视图
    }
    失败(“未找到匹配项”)
    }
    }
    
    }


    基于本文的解决方案>

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

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

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

    上面的解决方案非常好,我在Kotlin又贡献了一个

    浓缩咖啡需要滚动到该位置,然后进行检查。要滚动到该位置,我正在使用。然后使用来执行检查。我没有直接调用
    findviewolderforadapterposition
    ,而是使用了一个更方便的函数

    在下面的示例代码中,Espresso滚动到
    RecyclerView
    的第43行,检查该行的
    ViewHolder
    是否有两个文本视图,以及这些文本视图是否分别包含文本“第43行的hello text 1”和“第43行的hello text 2”:

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

    当您检查您的项目(文本视图中的文本)是否在RecyclerView中处于第一位时,修改了matcher解决方案

    // 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?) {}
        }
    }
    

    基于Kotlin用户的->

    : 我知道我已经很晚了,我很困惑为什么人们要自定义RecycleView操作。我有
    androidTestImplementation "androidx.test.espresso:espresso-contrib:3.2.0"
    androidTestImplementation "it.xabaras.android.espresso:recyclerview-child-actions:1.0"
    
    // 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?) {}
        }
    }
    
        onView(withText("TEXT_VIEW_TEXT")).check(matches(
            customMatcherForFirstItem("TEXT_VIEW_TEXT", withParent(withId(R.id.recycler_view)))))
    
    onView(withId(R.id.recycler_view))
     .perform(actionOnItemAtPosition<RecyclerView.ViewHolder>(46, scrollTo()))
           .check(matches(hasDescendant(withText("TextToMatch"))))