Java 如何在 Espresso 中的 RecyclerView 内断言?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31394569/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 11:02:39  来源:igfitidea点击:

How to assert inside a RecyclerView in Espresso?

javaandroidandroid-espresso

提问by juhlila

I am using espresso-contrib to perform actions on a RecyclerView, and it works as it should, ex:

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

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

and I need to perform assertions on it. Something like this:

我需要对其进行断言。像这样的东西:

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

but, because RecyclerViewActionsis, of course, expecting an action, it says wrong 2nd argument type. There's no RecyclerViewAssertionson espresso-contrib.

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

Is there any way to perform assertions on a recycler view?

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

采纳答案by HowieH

You should check out Danny Roa's solution Custom RecyclerView ActionsAnd use it like this:

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

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

回答by riwnodennyk

Pretty easy. No extra library is needed. Do:

挺容易。不需要额外的库。做:

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

if your ViewHolder uses ViewGroup, wrap withText()with a hasDescendant()like:

如果您ViewHolder使用ViewGroup中,包装withText()hasDescendant(),如:

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

with method you may put into your Utilsclass.

您可以将方法放入您的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);
        }
    };
}

If your item may be not visible on the screen at first, then scroll to it before:

如果您的项目一开始可能在屏幕上不可见,请先滚动到它:

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

回答by You Qi

Just to enhance riwnodennyk's answer, if your ViewHolderis a ViewGroupinstead of a direct Viewobject like TextView, then you can add hasDescendantmatcher to match the TextViewobject in the ViewGroup. Example:

只是为了增强riwnodennyk的答案,如果你的ViewHolderViewGroup不是一个直接的View喜欢对象TextView,那么你可以添加hasDescendant匹配匹配TextView的对象ViewGroup。例子:

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

回答by I_AM_PANDA

Danny Roa's solution is awesome, but it didn't work for the off-screen items I had just scrolled to. The fix is replacing this:

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

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

with this:

有了这个:

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

... in the TestUtilsclass.

...在TestUtils课堂上

回答by Vince

In my case, it was necessary to merge Danny Roa's and riwnodennyk's solution:

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

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

and the method :

和方法:

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

回答by Hiten Bahri

I have been struggling on the same issue where I have a recycler view with 6 items and then I have to choose the one at position 5 and the 5th one is not visible on the view.
The above solution also works. Also, I know its a bit late but thought its worth sharing.

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

I did this with a simple solution as below:

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

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

RecyclerViewActions.actionOnItem- Performs a ViewActionon a view matched by viewHolderMatcher.

RecyclerViewActions.actionOnItem-ViewAction对匹配的视图执行 a viewHolderMatcher

  1. Scroll RecyclerViewto the view matched by itemViewMatcher
  2. Perform an action on the matched view
  1. 滚动RecyclerView到匹配的视图itemViewMatcher
  2. 对匹配的视图执行操作

More details can be found at : RecyclerViewActions.actionOnItem

可以在以下位置找到更多详细信息:RecyclerViewActions.actionOnItem

回答by Max Buster

This thread is pretty old but I recently extended the RecyclerViewAction support to be usable for assertions as well. This allows you to test off-screen elements without specifying a position while still using view matchers.

这个线程已经很老了,但我最近扩展了 RecyclerViewAction 支持,使其也可用于断言。这允许您在不指定位置的情况下测试屏幕外元素,同时仍然使用视图匹配器。

Check out this article > Better Android RecyclerView Testing!

查看这篇文章 >更好的 Android RecyclerView 测试

回答by Alexandr Larin

For testing all items (include off-screen) use code bellow

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

Sample:

样本:

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

Class:

班级:

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

}

}

Solution based of this article > Better Android RecyclerView Testing!

基于本文的解决方案 >更好的 Android RecyclerView 测试!

回答by Thomas

I combined a few answers above into a reusable method for testing other recycler views as the project grows. Leaving the code here in the event it helps anyone else out...

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

Declare a function that may be called on a RecyclerView Resource ID:

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

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

Then call it on your recycler view to check the text displayed of multiple adapter items in the list at a given position by doing:

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

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

回答by Michael Osofsky

The solutions above are great, I'm contributing one more in Kotlin.

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

Espresso needs to scroll to the position and then check it. To scroll to the position, I'm using RecyclerViewActions.scrollToPosition. Then use findViewHolderForAdapterPositionto perform the check. Instead of calling findViewHolderForAdapterPositiondirectly, I am using a nicer convenience function childOfViewAtPositionWithMatcher.

Espresso 需要滚动到该位置,然后检查它。要滚动到该位置,我正在使用RecyclerViewActions.scrollToPosition. 然后使用 findViewHolderForAdapterPosition执行检查。findViewHolderForAdapterPosition我没有直接调用,而是使用了一个更好的便捷函数childOfViewAtPositionWithMatcher

In the example code below, Espresso scrolls to row 43 of the RecyclerViewand the checks that the row's ViewHolderhas two TextViews and that those TextViews contain the text "hello text 1 at line 43" and "hello text 2 at line 43" respectively:

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

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

// ...

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

Here are my build.gradle dependencies (newer versions may exist):

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

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