How to access RecyclerView ViewHolder with Espresso?

后端 未结 3 1137
梦毁少年i
梦毁少年i 2020-12-09 23:24

I want to test the text contained in each ViewHolder of my RecyclerView:

@RunWith(AndroidJUnit4.class)
public class EspressoTest {
         


        
相关标签:
3条回答
  • 2020-12-09 23:53

    RecyclerViewMatcher from @Martin Zeitler's answer with more informative error reporting.

    import android.view.View;
    
    import android.content.res.Resources;
    import androidx.recyclerview.widget.RecyclerView;
    
    import org.hamcrest.Description;
    import org.hamcrest.Matcher;
    import org.hamcrest.TypeSafeMatcher;
    
    import static com.google.common.base.Preconditions.checkState;
    
    public class RecyclerViewMatcher {
    
        public static final int UNSPECIFIED = -1;
        private final int recyclerId;
    
        public RecyclerViewMatcher(int recyclerViewId) {
            this.recyclerId = recyclerViewId;
        }
    
        public Matcher<View> atPosition(final int position) {
            return atPositionOnView(position, UNSPECIFIED);
        }
    
        public Matcher<View> atPositionOnView(final int position, final int targetViewId) {
            return new TypeSafeMatcher<View>() {
                Resources resources;
                RecyclerView recycler;
                RecyclerView.ViewHolder holder;
    
                @Override
                public void describeTo(Description description) {
                    checkState(resources != null, "resource should be init by matchesSafely()");
    
                    if (recycler == null) {
                        description.appendText("RecyclerView with " + getResourceName(recyclerId));
                        return;
                    }
    
                    if (holder == null) {
                        description.appendText(String.format(
                                "in RecyclerView (%s) at position %s",
                                getResourceName(recyclerId), position));
                        return;
                    }
    
                    if (targetViewId == UNSPECIFIED) {
                        description.appendText(
                                String.format("in RecyclerView (%s) at position %s",
                                getResourceName(recyclerId), position));
                        return;
                    }
    
                    description.appendText(
                            String.format("in RecyclerView (%s) at position %s and with %s",
                                    getResourceName(recyclerId),
                                    position,
                                    getResourceName(targetViewId)));
                }
    
                private String getResourceName(int id) {
                    try {
                        return "R.id." + resources.getResourceEntryName(id);
                    } catch (Resources.NotFoundException ex) {
                        return String.format("resource id %s - name not found", id);
                    }
                }
    
                @Override
                public boolean matchesSafely(View view) {
                    resources = view.getResources();
                    recycler = view.getRootView().findViewById(recyclerId);
                    if (recycler == null)
                        return false;
                    holder = recycler.findViewHolderForAdapterPosition(position);
                    if (holder == null)
                        return false;
    
                    if (targetViewId == UNSPECIFIED) {
                        return view == holder.itemView;
                    } else {
                        return view == holder.itemView.findViewById(targetViewId);
                    }
                }
            };
        }
    }
    
    0 讨论(0)
  • 2020-12-09 23:56

    You can easily access to the viewHolder witht the recyclerView template onView(withId(R.id.your_list_id)).perform(actionOnItem<RecyclerView.ViewHolder>(withText(the_text_you_want), click()))

    0 讨论(0)
  • 2020-12-10 00:04

    Espresso package espresso-contrib is necessary, because it provides those RecyclerViewActions, which do not support assertions.

    import android.support.test.espresso.contrib.RecyclerViewActions;
    import android.support.test.rule.ActivityTestRule;
    import android.support.test.runner.AndroidJUnit4;
    
    import org.hamcrest.Description;
    import org.hamcrest.Matcher;
    import org.hamcrest.TypeSafeMatcher;
    import org.junit.Before;
    import org.junit.Rule;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    
    import static android.support.test.espresso.Espresso.onView;
    import static android.support.test.espresso.action.ViewActions.click;
    import static android.support.test.espresso.assertion.ViewAssertions.matches;
    import static android.support.test.espresso.matcher.ViewMatchers.withId;
    import static android.support.test.espresso.matcher.ViewMatchers.withText;
    
    @RunWith(AndroidJUnit4.class)
    public class TestIngredients {
    
        /** the Activity of the Target application */
        private IngredientsActivity mActivity;
    
        /** the {@link RecyclerView}'s resource id */
        private int resId = R.id.recyclerview_ingredients;
    
        /** the {@link RecyclerView} */
        private IngredientsLinearView mRecyclerView;
    
        /** and it's item count */
        private int itemCount = 0;
    
        /**
         * such a {@link ActivityTestRule} can be used eg. for Intent.putExtra(),
         * alike one would pass command-line arguments to regular run configurations.
         * this code runs before the {@link FragmentActivity} is being started.
         * there also would be an {@link IntentsTestRule}, but not required here.
        **/
        @Rule
        public ActivityTestRule<IngredientsActivity> mActivityRule = new ActivityTestRule<IngredientsActivity>(IngredientsActivity.class) {
    
            @Override
            protected Intent getActivityIntent() {
                Intent intent = new Intent();
                Bundle extras = new Bundle();
                intent.putExtras(extras);
                return intent;
            }
        };
    
        @Before
        public void setUpTest() {
    
            /* obtaining the Activity from the ActivityTestRule */
            this.mActivity = this.mActivityRule.getActivity();
    
            /* obtaining handles to the Ui of the Activity */
            this.mRecyclerView = this.mActivity.findViewById(this.resId);
            this.itemCount = this.mRecyclerView.getAdapter().getItemCount();
        }
    
        @Test
        public void RecyclerViewTest() {
            if(this.itemCount > 0) {
                for(int i=0; i < this.itemCount; i++) {
    
                    /* clicking the item */
                    onView(withId(this.resId))
                      .perform(RecyclerViewActions.actionOnItemAtPosition(i, click()));
    
                    /* check if the ViewHolder is being displayed */
                    onView(new RecyclerViewMatcher(this.resId)
                      .atPositionOnView(i, R.id.cardview))
                      .check(matches(isDisplayed()));
    
                    /* checking for the text of the first one item */
                    if(i == 0) {
                        onView(new RecyclerViewMatcher(this.resId)
                          .atPositionOnView(i, R.id.ingredientName))
                          .check(matches(withText("Farbstoffe")));
                    }
    
                }
            }
        }
    }
    

    Instead one can use a RecyclerViewMatcher for that:

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

    0 讨论(0)
提交回复
热议问题