Espresso - Check RecyclerView items are ordered correctly

后端 未结 5 2063
一个人的身影
一个人的身影 2021-01-05 03:18

How to go about checking whether RecyclerView items are displayed in the correct order using Espresso? I\'m trying to test it checking it by the text for the title of each e

相关标签:
5条回答
  • 2021-01-05 03:43

    I simplified a bit Mosius answer:

    public static Matcher<View> hasItemAtPosition(final Matcher<View> matcher, final int position) {
        return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
            @Override
            public void describeTo(Description description) {
                description.appendText("has item at position " + position + ": ");
                matcher.describeTo(description);
            }
            @Override
            protected boolean matchesSafely(RecyclerView recyclerView) {
                RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position);
                return matcher.matches(viewHolder.itemView);
            }
        };
    }
    

    We pass Matcher to the function so we can provide further conditions. Example usage:

    onView(hasItemAtPosition(hasDescendant(withText("Item 1")), 0)).check(matches(isDisplayed()));
    onView(hasItemAtPosition(hasDescendant(withText("Item 2")), 1)).check(matches(isDisplayed()));
    
    0 讨论(0)
  • 2021-01-05 03:46

    The original problem has been solved but am posting an answer here as found the Barista library solves this problem in one single line of code.

    assertDisplayedAtPosition(R.id.rv_metrics, 0, R.id.tv_title, "weight");
    

    It's made on top of Espresso and the documentation for it can be found here

    Hope this may be helpful to someone. :)

    0 讨论(0)
  • 2021-01-05 03:50

    If somebody is interested in the Kotlin version, here it is

    fun hasItemAtPosition(position: Int, matcher: Matcher<View>) : Matcher<View> {
        return object : BoundedMatcher<View, RecyclerView>(RecyclerView::class.java) {
    
            override fun describeTo(description: Description?) {
                description?.appendText("has item at position $position : ")
                matcher.describeTo(description)
            }
    
            override fun matchesSafely(item: RecyclerView?): Boolean {
                val viewHolder = item?.findViewHolderForAdapterPosition(position)
                return matcher.matches(viewHolder?.itemView)
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-05 03:56

    As it's been mentioned here, RecyclerView objects work differently than AdapterView objects, so onData() cannot be used to interact with them.

    In order to find a view at specific position of a RecyclerView you need to implement a custom RecyclerViewMatcher like below:

    public class RecyclerViewMatcher {
        private final int recyclerViewId;
    
        public RecyclerViewMatcher(int recyclerViewId) {
            this.recyclerViewId = recyclerViewId;
        }
    
        public Matcher<View> atPosition(final int position) {
            return atPositionOnView(position, -1);
        }
    
        public Matcher<View> atPositionOnView(final int position, final int targetViewId) {
    
            return new TypeSafeMatcher<View>() {
                Resources resources = null;
                View childView;
    
                public void describeTo(Description description) {
                    String idDescription = Integer.toString(recyclerViewId);
                    if (this.resources != null) {
                        try {
                            idDescription = this.resources.getResourceName(recyclerViewId);
                        } catch (Resources.NotFoundException var4) {
                            idDescription = String.format("%s (resource name not found)",
                                                          new Object[] { Integer.valueOf
                                                              (recyclerViewId) });
                        }
                    }
    
                    description.appendText("with id: " + idDescription);
                }
    
                public boolean matchesSafely(View view) {
    
                    this.resources = view.getResources();
    
                    if (childView == null) {
                        RecyclerView recyclerView =
                            (RecyclerView) view.getRootView().findViewById(recyclerViewId);
                        if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
                            childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
                        }
                        else {
                            return false;
                        }
                    }
    
                    if (targetViewId == -1) {
                        return view == childView;
                    } else {
                        View targetView = childView.findViewById(targetViewId);
                        return view == targetView;
                    }
    
                }
            };
        }
    }
    

    And then use it in your test case in this way:

    @Test
    void testCase() {
        onView(new RecyclerViewMatcher(R.id.rv_metrics)
            .atPositionOnView(0, R.id.txt_title))
            .check(matches(withText("Weight")))
            .perform(click());
    
        onView(new RecyclerViewMatcher(R.id.rv_metrics)
            .atPositionOnView(1, R.id.txt_title))
            .check(matches(withText("Height")))
            .perform(click());
    }
    
    0 讨论(0)
  • 2021-01-05 04:07

    If you want to match a matcher on a position in RecyclerView, then you can try to create a custom Matcher<View>:

    public static Matcher<View> hasItemAtPosition(int position, Matcher<View> matcher) {
        return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
    
            @Override public void describeTo(Description description) {
                description.appendText("has item: ");
                matcher.describeTo(description);
                description.appendText(" at position: " + position);
            }
    
            @Override protected boolean matchesSafely(RecyclerView view) {
                RecyclerView.Adapter adapter = view.getAdapter();
                int type = adapter.getItemViewType(position);
                RecyclerView.ViewHolder holder = adapter.createViewHolder(view, type);
                adapter.onBindViewHolder(holder, position);
                return matcher.matches(holder.itemView);
            }
        };
    }
    

    And you can use it for example:

    onView(withId(R.id.rv_metrics)).check(matches(0, hasDescendant(withText("Weight")))))
    
    0 讨论(0)
提交回复
热议问题