Custom View Matchers


Advertisements

Espresso provides various options to create our own custom view matchers and it is based on Hamcrest matchers. Custom matcher is a very powerful concept to extend the framework and also to customize the framework to our taste. Some of the advantages of writing custom matchers are as follows,

  • To exploit the unique feature of our own custom views

  • Custom matcher helps in the AdapterView based test cases to match with the different type of underlying data.

  • To simplify the current matchers by combining features of multiple matcher

We can create new matcher as and when the demand arises and it is quite easy. Let us create a new custom matcher, which returns a matcher to test both id and text of a TextView.

Espresso provides the following two classes to write new matchers −

  • TypeSafeMatcher

  • BoundedMatcher

Both classes are similar in nature except that the BoundedMatcher transparently handles the casting of the object to correct type without manually checking for the correct type. We will create a new matcher, withIdAndText using BoundedMatcher class. Let us check the steps to write new matchers.

  • Add the below dependency in the app/build.gradle file and sync it.

dependencies {
   implementation 'androidx.test.espresso:espresso-core:3.1.1'
}
  • Create a new class to include our matchers (methods) and mark it as final

public final class MyMatchers {
}
  • Declare a static method inside the new class with the necessary arguments and set Matcher<View> as return type.

public final class MyMatchers {
   @NonNull
   public static Matcher<View> withIdAndText(final Matcher<Integer>
   integerMatcher, final Matcher<String> stringMatcher) {
   }
}
  • Create a new BoundedMatcher object (return value as well) with the below signature inside the static method,

public final class MyMatchers {
   @NonNull
   public static Matcher<View> withIdAndText(final Matcher<Integer>
   integerMatcher, final Matcher<String> stringMatcher) {
      return new BoundedMatcher<View, TextView>(TextView.class) {
      };
   }
}
  • Override describeTo and matchesSafely methods in the BoundedMatcher object. describeTo has single argument of type Description with no return type and it is used to error information regarding matchers. matchesSafely has a single argument of type TextView with return type boolean and it is used to match the view.

The final version of the code is as follows,

public final class MyMatchers {
   @NonNull
   public static Matcher<View> withIdAndText(final Matcher<Integer>
   integerMatcher, final Matcher<String> stringMatcher) {
      return new BoundedMatcher<View, TextView>(TextView.class) {
         @Override
         public void describeTo(final Description description) {
            description.appendText("error text: ");
            stringMatcher.describeTo(description);
            integerMatcher.describeTo(description);
         }
         @Override
         public boolean matchesSafely(final TextView textView) {
            return stringMatcher.matches(textView.getText().toString()) &&
            integerMatcher.matches(textView.getId());
         }
      };
   }
}
  • Finally, We can use our mew matcher to write the test case as sown below,

@Test
public void view_customMatcher_isCorrect() {
   onView(withIdAndText(is((Integer) R.id.textView_hello), is((String) "Hello World!")))
      .check(matches(withText("Hello World!")));
}
Advertisements