Neon Warge Neon Warge - 2 months ago 40
Android Question

How to check if a View is VISIBLE on a specific RecyclerView item?

I am new to using espresso. I cannot really get what I wanted to test using this code:

onData(withId(R.id.relativelayout_tag))
.inAdapterView(withId(R.id.recyclerview_tag_list))
.onChildView(withId(R.id.imageview_tag))
.atPosition(1)
.check(matches(isDisplayed()));


Where
R.id.imageview_tag
is a child of
R.id.relativelayout_tag
.
R.id.relativelayout_tag
holds the entire content for my adapter item.
R.id.recyclerview_tag_list
is the name of my
RecyclerView
on which I assigned a particular
RecyclerView
Adapter
.

This is a very very basic test. Here are the user procedures:


  1. Select, SPECIFICALLY, the first item on the
    RecyclerView
    (I don't really care what
    text is on the view). Also do not suggest using view text to identify the first item. I DONT CARE ABOUT THE ADAPTER ITEM'S CONTENT or even putting a unique tag on some view.

  2. On select, the indicator view(An ImageView that shows that the item
    is selected) should appear.



Very basic and simple. It is so hard to write a test for this basic user story using Espresso. When I run that particular test it always fail stating this:

Caused by: java.lang.RuntimeException: Action will not be performed because the target view does not match one or more of the following constraints:
(is assignable from class: class android.widget.AdapterView and is displayed on the screen to the user)
Target view: "RecyclerView{id=2131624115, res-name=recyclerview_tag_list, visibility=VISIBLE, width=480, height=1032, has-focus=false, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=15}"


It doesn't make sense as I have the list already visible. I can even run this test just fine:

onView(withId(R.id.recyclerview_tag_list))
.perform(RecyclerViewActions
.actionOnItemAtPosition(1, click()));


Here is the full test:

@Test
public void shouldTagToggleSelected()
{
onView(withId(R.id.recyclerview_tag_list))
.perform(RecyclerViewActions
.actionOnItemAtPosition(1, click()));

onData(withId(R.id.relativelayout_tag))
.inAdapterView(withId(R.id.recyclerview_tag_list))
.onChildView(withId(R.id.imageview_tag))
.atPosition(1)
.check(matches(isDisplayed()));

//onView(withId(R.id.imageview_tag))
// .check(matches(isDisplayed()));
}


What I wanted to test if the indicator view has a visibility set to
visible
ONLY on that particular item (or any item of my choosing).

Any thoughts? Maybe I missed something dearly.

Thanks a lot!

Answer

onData does not work with RecyclerView as RecyclerView does not extend AdapterView.

You need to use onView to make your assertions. If it's the first item in the recycler view, then you can use something like this matcher to make your assertion:

public static Matcher<View> withViewAtPosition(final int position, final Matcher<View> itemMatcher) {
        return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
            @Override
            public void describeTo(Description description) {
                itemMatcher.describeTo(description);
            }

            @Override
            protected boolean matchesSafely(RecyclerView recyclerView) {
                final RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position);
                return viewHolder != null && itemMatcher.matches(viewHolder.itemView);
            }
        };
}

And the usage is the following:

    onView(withId(R.id.recyclerview_tag_list))
            .check(matches(withViewAtPosition(1, hasDescendant(allOf(withId(R.id.imageview_tag), isDisplayed())))));

Keep in mind that this matcher will fail if your ViewHolder has not been laid out yet. If that's the case you need to scroll to the ViewHolder with RecyclerViewActions. You don't need to scroll if you are clicking the item in the test prior to using the matcher.