Espresso UI Tests and the RecyclerView


The problem with Ui Tests and the RecyclerView arise with all items that are not visible or below the visible area. Everything that is not visible are not in the View-Tree and so to speak: untestable! You have to scroll to the item position and check the view visibility afterwards. There are of course some libraries that are trying to help you. So lets start with a quick RecyclerView Espresso tutorial:

First import the support RecyclerView Espresso library that gives you access to the RecyclerViewAction class in your gradle file:

androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.1') {
  // Necessary to avoid version conflicts
  exclude group: 'com.android.support', module: 'appcompat'
  exclude group: 'com.android.support', module: 'support-v4'
  exclude group: 'com.android.support', module: 'support-annotations'
  exclude module: 'recyclerview-v7'
}

Let's start with scrolling to a defined list position using the 'scrollToPosition' method:

onView(withId(R.id.recyclerView)).perform(RecyclerViewActions.scrollToPosition(3));

This should work out of the box but it's a bit optimistic, since you scroll to a specific list position. But what if you don't know the exact item position?

'scrollToHolder' will do the trick for you.

In my example the list-item layout looks like this: 


In my test-case i want to confirm that the "All-day event"-item is visible. The first thing you should ever do is: scroll to the position! There are so many devices that have small screens and you really don't know if it's visible or not. 

To scroll to a item position use the RecyclerView-id again and perform the scroll to holder action:

onView(withId(R.id.recyclerView)).perform(RecyclerViewActions.scrollToHolder(...));

Now the tricky part: We need to provide a ViewHolderMatcher which returns a matcher that holds the ViewHolder for your specific RecyclerView. In my example i use a DateOptionViewHolder which holds all Text- and ImageViews. 

Create a new method and return a new BoundedMatcher with the parent ViewHolder class and your own ViewHolder:


new BoundedMatcher<RecyclerView.ViewHolder, DateOptionViewHolder>(DateOptionViewHolder.class) {...}

implement all methods and use the matchesSafely method to search for the view and your text:


@Override
protected boolean matchesSafely(DateOptionViewHolder item) {
    TextView timeViewText = (TextView) item.itemView.findViewById(R.id.tv_po_option_time);
    if (timeViewText == null) {
        return false;
    }
    return timeViewText.getText().toString().contains(text);
}

The code should look like this:

Define your custom matcher:

public static Matcher<RecyclerView.ViewHolder> withHolderTimeView(final String text) {
    return new BoundedMatcher<RecyclerView.ViewHolder, DateOptionViewHolder>(DateOptionViewHolder.class) {

        @Override
        public void describeTo(Description description) {
            description.appendText("No ViewHolder found with text: " + text);
        }

        @Override
        protected boolean matchesSafely(DateOptionViewHolder item) {
            TextView timeViewText = (TextView) item.itemView.findViewById(R.id.tv_po_option_time);
            if (timeViewText == null) {
                return false;
            }
            return timeViewText.getText().toString().contains(text);
        }
    };
}

scroll to the View in your test:

onView(withId(R.id.recyclerView)).perform(
    RecyclerViewActions.scrollToHolder(
        withHolderTimeView(getActivity().getString(R.string.all_day_event))
    )
);

Cheers,
Alexander Thiele
Espresso UI Tests and the RecyclerView Espresso UI Tests and the RecyclerView Reviewed by SolCast on 22:04 Rating: 5

5 comments:

Powered by Blogger.