Android UI Test — Espresso Matcher for ImageView

Daniele Bottillo
3 min readOct 25, 2015

--

EDIT: this post has been update to support Vector drawables, thanks to Enrico Gueli

Espresso is one of my favourite tool for testing and recently I realised that doesn’t have any matcher for ImageViews!

Imagine to have a card number field and you want to display a little icon on the left of the field with the type of the card (Visa, MasterCard, etc..).
It would be nice to do something like:

onView(withId(R.id.indicator)).check(matches(withDrawable(R.drawable.mastercard)));

Unfortunately there is no ‘withDrawable’ out of the box with Espresso, but it’s quite easy to create your own version.

First of all you need to create static methods:

public class EspressoTestsMatchers {

public static Matcher<View> withDrawable(final int resourceId) {
return new DrawableMatcher(resourceId);
}

public static Matcher<View> noDrawable() {
return new DrawableMatcher(-1);
}
}

I like the idea of having also a ‘noDrawable()’ method just in case you want to test that the ImageView doesn’t have any image associated.
Next step is to create the DrawableMatcher, where all the magic happens:

public class DrawableMatcher extends TypeSafeMatcher<View> {
@Override
protected boolean matchesSafely(View item) {
return false;
}

@Override
public void describeTo(Description description) {

}
}

To create your own Matcher you need to extend ‘TypeSafeMatcher’ which requires to implement ‘matchesSafely’ and ‘describeTo’. The first method is where you have the chance to do the actual test, ‘describeTo’ instead is for espresso when needs to print the output’s matcher.

As you have seen previously, we were creating DrawableMatcher passing the id of the drawable that we are expecting:

private final int expectedId;

public DrawableMatcher(int resourceId) {
super(View.class);
this.expectedId = resourceId;
}

Now that we have the expected id we can actually check the image of the ImageView:

@Override
protected boolean matchesSafely(View target) {
if (!(target instanceof ImageView)){
return false;
}
ImageView imageView = (ImageView) target;
if (expectedId < 0){
return imageView.getDrawable() == null;
}
Resources resources = target.getContext().getResources();
Drawable expectedDrawable = resources.getDrawable(expectedId);
if (expectedDrawable == null) {
return false;
}
Bitmap bitmap = getBitmap (imageView.getDrawable());
Bitmap otherBitmap = getBitmap(expectedDrawable);
return bitmap.sameAs(otherBitmap);
}
private Bitmap getBitmap(Drawable drawable){
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}

The method accesses the context of the View target which is very important: don’t try to use any other context or you will not find the expected Id from ‘resources.getDrawable(expectedId)!

EDIT: “Then it’s just a matter of create and compare the two bitmaps.” -> With vector drawables is a bit more complicated than that because you need to create the Bitmap from the vector. So instead of merely calling ‘getBitmap()’ on the drawable it’s better to create a bitmap from scratch and drawing the drawable into it. Of course ‘getBitmap()’ from a VectorDrawable or a VectorDrawableCompat doesn’t exist at all.

Finally in the describeTo method we can just add a description for our matcher:

@Override
public void describeTo(Description description) {
description.appendText("with drawable from resource id: ");
description.appendValue(expectedId);
if (resourceName != null) {
description.appendText("[");
description.appendText(resourceName);
description.appendText("]");
}
}

So back to the initial example, now you can do:

onView(withId(R.id.card_number)).perform(typeText("5000"));
onView(withId(R.id.card_type_indicator)).check(matches(withDrawable(R.drawable.mastercard)));
onView(withId(R.id.card_number)).perform(clearText());
onView(withId(R.id.card_number)).perform(typeText("4000"));
onView(withId(R.id.card_type_indicator)).check(matches(withDrawable(R.drawable.visa)));
onView(withId(R.id.card_number)).perform(clearText());
onView(withId(R.id.card_number)).perform(typeText("3400"));
onView(withId(R.id.card_type_indicator)).check(matches(withDrawable(R.drawable.amex)));
onView(withId(R.id.card_number)).perform(clearText());
onView(withId(R.id.card_number)).perform(typeText("0000"));
onView(withId(R.id.card_type_indicator)).check(matches(noDrawable()));

Here you can find the full class of the matcher: https://github.com/dbottillo/Blog/blob/espresso_match_imageview/app/src/androidTest/java/com/danielebottillo/blog/config/DrawableMatcher.java

--

--

Daniele Bottillo

Android@Monzo Bank (London), technology enthusiast, book reader, TV shows fan, game player, Lego addicted.