Search code examples
androidandroid-recyclerviewandroid-espresso

Espresso - Check RecyclerView items are ordered correctly


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 element.

When I try this piece of code it works to click the element but can't go on to instead of performing a click trying to Assert the text for the element

onView(withId(R.id.rv_metrics)).perform(actionOnItemAtPosition(0, click()));

When I try to use a custom matcher instead I keep getting the error

Error performing 'load adapter data' on view 'with id: mypackage_name:id/rv_metrics'

I know now onData doesn't work for RecyclerView but before that I was trying to use a custom matcher for this task.

 public static Matcher<Object> hasTitle(final String inputString) {
    return new BoundedMatcher<Object, Metric>(Metric.class) {
        @Override
        protected boolean matchesSafely(Metric metric) {

            return inputString.equals(metric.getMetric());

        }

        @Override
        public void describeTo(org.hamcrest.Description description) {
            description.appendText("with title: ");
        }
    };
}

I also tried something like this but it obviously doesn't work due to the type given as parameter to the actionOnItemAtPosition method but would we have something similar to it that could maybe work?

onView(withId(R.id.rv_metrics)).check(actionOnItemAtPosition(0, ViewAssertions.matches(withText("Weight"))));

What am I missing here please? Thanks a lot.


Solution

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