Android Espresso(五) ——Custom Matcher

Android Espresso(五) ——Custom Matcher

上一篇(Android Espresso(四)——RecyclerView)提到了使用自定义 ViewAction 来灵活应对 RecyclerView 中复杂Item测试。

这一篇讲下,在某种场景下,相同文字不同颜色或者其他属性,匹配其中一个组件进行操作,使用自定义ViewMatcher


自定义Matcher

在匹配一些简单的UI组件上,可以使用 BoundedMatcher 快速定义自己的 Matcher

static class CustomViewMatchers {

        public static Matcher<View> withColoredText(final int expectedColor) {
            return new BoundedMatcher<View, TextView>(TextView.class) {
                @Override
                public void describeTo(Description description) { // 为matcher添加描述信息
                    description.appendText("To find the textview with " + expectedColor + " color text");
                }

                // 主要实现这个matchesSafely()方法,来实现自己想要获取到的View。
                // 这里需要注意参数类型 TextView 与 BoundedMatcher构造方法传入的 TextView在定义上是不同的。
                // 见下面BoundedMatcher定义
                @Override
                protected boolean matchesSafely(TextView item) {
                    return expectedColor == item.getCurrentTextColor();
                }
            };
        }
    }

上述这段代码,可以快速看下如何使用 BoundedMatcher 来定义自己的 Matcher。


BoundedMatcher

先来看下 BoundedMatcher 定义。

// 在创建BoundedMatcher实例时,需要传入泛型类型T,S.
// T 表示Matcher要处理的类型,S 表示的操作实际的T的子类型
public abstract class BoundedMatcher<T, S extends T> extends BaseMatcher<T> {

  private final Class<?> expectedType;
  private final Class<?>[] interfaceTypes;

  public BoundedMatcher(Class<? extends S> expectedType) {
    this.expectedType = checkNotNull(expectedType);
    this.interfaceTypes = new Class[0];
  }

  public BoundedMatcher(
      Class<?> expectedType, Class<?> interfaceType1, Class<?>... otherInterfaces) {
    this.expectedType = checkNotNull(expectedType);
    checkNotNull(otherInterfaces);
    int interfaceCount = otherInterfaces.length + 1;
    this.interfaceTypes = new Class[interfaceCount];

    interfaceTypes[0] = checkNotNull(interfaceType1);
    checkArgument(interfaceType1.isInterface());
    int interfaceTypeIdx = 1;
    for (Class<?> intfType : otherInterfaces) {
      interfaceTypes[interfaceTypeIdx] = checkNotNull(intfType);
      checkArgument(intfType.isInterface());
      interfaceTypeIdx++;
    }
  }

  // 这里参数item的类型是S, 在创建BoundedMatcher实例时传入的泛型S
  protected abstract boolean matchesSafely(S item);

  // 真正的逻辑处理,判断传入的expectedType是否是同时传入的 interfaceTypes 中的类型,
  // 满足条件之后,调用matchesSafely()方法进行匹配。
  @Override
  @SuppressWarnings({"unchecked"})
  public final boolean matches(Object item) {
    if (item == null) {
      return false;
    }

    if (expectedType.isInstance(item)) {
      for (Class<?> intfType : interfaceTypes) {
        if (!intfType.isInstance(item)) {
          return false;
        }
      }
      return matchesSafely((S) item);
    }
    return false;
  }
}

BoundedMatcher本身的定义中包含了两个构造方法,第二个构造方法中可以同时传入多个需要进行匹配的类型。我们在上述实现使用的是单个参数的构造方法。

@Test
public void testCustomMatcher() throws InterruptedException {
    Activity activity = activityTestRule.getActivity();
    onView(withId(R.id.textview_red_text)).check(matches(withColoredText(activity.getColor(android.R.color.holo_red_dark)))).perform(click());
    Thread.sleep(1000); // 为了执行看清UI变化

    // 点击button,修改TextView文字颜色
    onView(withId(R.id.button_change_color)).check(matches(isAssignableFrom(Button.class))).perform(click());
    Thread.sleep(1000);

    // 判断修改后的问题是否是预期的颜色
    onView(withId(R.id.textview_common_text)).check(matches(withColoredText(Color.BLUE))).perform(click());
}

这里直接使用check()方法来检查传入的颜色是否与ID匹配的TextView颜色形同。

看下运行效果。
在这里插入图片描述


RecyclerView Matcher

要在RecyclerView上无法简单使用ViewMatcher找到 Item,那么就需要自定义RecyclerView使用的 Matcher。

结合 RecyclerViewActions中定义的action方法(基于position, viewholder的参数),可以使用BuondedMatcher来自定义Matcher。

可以先看如下定义:

static class CustomRecyclerViewMatchers {
	public static Matcher<RecyclerView.ViewHolder> withTitle(final String expectedTitle) {
		Checks.checkNotNull(expectedTitle);

		return new BoundedMatcher<RecyclerView.ViewHolder, CustomAdapter.ViewHolder>(CustomAdapter.ViewHolder.class) {

			@Override
			public void describeTo(Description description) {
				description.appendText("Find ViewHolder with title: " + expectedTitle);
			}

			@Override
			protected boolean matchesSafely(CustomAdapter.ViewHolder item) {
				String holderTitle = item.textView.getText().toString();
				return TextUtils.equals(expectedTitle, holderTitle);
			}
		};
	}
}

在构造BuondedMatcher时,传入的类型分别是RecyclerView.ViewHolderCustomAdapter.ViewHolder,参数传入的对比类型即是CustomAdapter.ViewHolder.class

在调用上,

    @Test
    public void testRecyclerViewCustomMatcher() {
        onView(withId(R.id.recyclerView)).perform(scrollToHolder(CustomRecyclerViewMatchers.withTitle("This is the middle"))).check(matches(isDisplayed()));
    }

上述调用中,首先在RecyclerView中找到 Title 值是 This is the middle 的 Item, 并判断是否显示。

这样一个自定义的 RecyclerView Matcher也就完成了。


总结

除了 BoundedMatcher,Espresso还提供其他诸如CustomMatcherTypeSafeMatcher等可用于自定义Matcher的定义。

这篇文章中只列举了BoundedMatcher的使用,其他Matcher的使用或执行原理,感兴趣的同学可以自己再查看下定义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VoidHope

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值