如何检查特定RecyclerView项目上的视图是否可见?

5

我是一个新手使用Espresso。我无法通过以下代码测试我想要的内容:

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

在这里,R.id.imageview_tagR.id.relativelayout_tag 的子项。而 R.id.relativelayout_tag 包含了适配器项目的全部内容。R.id.recyclerview_tag_list 是我赋予特定 RecyclerView 适配器的名称。

这是一个非常基础的测试。以下是用户流程:

  1. 选择 RecyclerView 上的第一项(我不在乎视图上显示的文本),也不要建议使用视图文本来标识第一项。我不关心适配器项目的内容,甚至不需要在某个视图上放置唯一标签。
  2. 选择后,指示器视图(一个显示所选项目的 ImageView)应该出现。

非常基础和简单。但是,使用 Espresso 编写这个基本用户故事的测试却很难。当我运行特定的测试时,它总是失败并报错如下:

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

我已经能看到列表,所以这没有意义。我甚至可以成功运行这个测试:

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

这是完整测试:

在这里输入内容

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

我想测试指示器视图是否仅在特定项目(或我选择的任何项目)中设置了可见性为visible。 你有什么想法吗?也许我错过了一些重要的东西。非常感谢!
2个回答

15

onData 无法与 RecyclerView 配合使用,因为 RecyclerView 没有扩展 AdapterView

您需要使用 onView 来进行断言。如果它是 RecyclerView 中的第一个项目,则可以使用类似以下 Matcher 的内容来进行断言:

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

并且使用方法如下:

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

请记住,如果您的ViewHolder尚未布局,则此匹配器将失败。如果是这种情况,您需要使用RecyclerViewActions滚动到ViewHolder。如果您在使用匹配器之前测试中单击了该项,则无需滚动。


嗨,我编辑了你的答案。它应该是onView(withId(R.id.recyclerview_tag_list)),我知道这就是你的意思。谢谢,现在我能通过测试了。我仍然有点难以理解BoundedMatchers。如果可能的话我会尽量避免使用它们。但似乎不是这种情况。 - Neon Warge

1

当检查不可见的线性布局元素时,我遇到了一个异常错误。为了解决这个问题,我不得不添加以下代码:

withFailureHandler
class MyTest {

        boolean isViewVisible = true;

        public void test1(){

           onView(withRecyclerView(R.id.recycler_view)
                .atPositionOnView(index, R.id.linear_layout))
                .withFailureHandler(new FailureHandler() {
                    @Override
                    public void handle(Throwable error, Matcher<View> viewMatcher){

                        isViewVisible = false;                            
                    }
                })
          .check(isVisible());

          //wait for failure handler to finish
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }

          if(isViewVisible){

              //do something
          }
        }
}

class MyTestUtil {

    public static RecyclerViewMatcher withRecyclerView(final int recyclerViewId) {

        return new RecyclerViewMatcher(recyclerViewId);
    }

    public static ViewAssertion isVisible() {

        return new ViewAssertion() {
            public void check(View view, NoMatchingViewException noView) {
                assertThat(view, new VisibilityMatcher(View.VISIBLE));
            }
        };
    }
}


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

            }
        };
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接