Espresso – Check RecyclerView items are ordered correctly

Issue

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

Answered By – Mosius

Leave a Comment