Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 330
Francislainy Campos Avatar asked Oct 10 '18 09:10

Francislainy Campos


4 Answers

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());
}
like image 146
Mosius Avatar answered Nov 18 '22 16:11

Mosius


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)
        }
    }
}
like image 34
jquecanb Avatar answered Nov 18 '22 16:11

jquecanb


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()));
like image 27
AppiDevo Avatar answered Nov 18 '22 15:11

AppiDevo


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. :)

like image 44
Francislainy Campos Avatar answered Nov 18 '22 15:11

Francislainy Campos