Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

How to Use Espresso for Android UI Testing

DZone's Guide to

How to Use Espresso for Android UI Testing

If you want to learn about UI testing your Android app with Espresso, this is the tutorial for you, covering the basics, AdapterViews, RecyclerViews, and more.

· Mobile Zone ·
Free Resource

I wrote about the basics of Espresso and how we can set it up and use it.

In this blog, we will look at how we can test other components using Espresso. We will be covering some basic tests we might need to write while writing test cases. We will also be covering how to test views that are not part of the default window hierarchy along with testing AdapterViews & RecyclerViews. Lastly, we will be covering how we can write custom Matchers and custom Failure Handlers.

Basic Tests

Let’s first look at some common tests we might need to write:

//Perform click event on view with id R.id.btn_login
onView(withId(R.id.btn_login))
.perform(click());

//Type text in view with id R.id.et_name
onView(withId(R.id.et_name))
.perform(typeText("Espresso"), closeSoftKeyboard());

//Check that text in view with id R.id.et_name
 contains text "Espresso"
onView(withId(R.id.et_name))
.check(matches(withText(containsString("Espresso"))));

//Perform click event on a view that is a sibling
 of another view
onView(allOf(withText("Espresso"),
            hasSibling(withText("sibling item: 10"))))
.perform(click());

//Asserting that a view is not displayed
onView(withId(R.id.btn_login))
.check(matches(not(isDisplayed())));

//Asserting that a view is not present
onView(withId(R.id.btn_login))
.check(doesNotExist());

Following the pattern above, we can write test cases according to our requirements

Test Views Outside Default Window Hierarchy

There might be a few cases in which we need to test views that are not part of the default window hierarchy. For example, testing a view that might have been rendered using WindowManager:

onView(withText("Espresso"))
.inRoot(withDecorView(
       not(is(getActivity().getWindow().getDecorView()))))
.perform(click());


Testing an AdapterView

Now let’s move on to AdapterView testing. To test AdapterView, Espresso provides a separate  onData() entry point which first brings the adapter item to be tested in focus before performing any operation on itself or its children. So in case we want to test an AdapterView, we need to use the onData() method instead of the onView()  method.

onData(ObjectMatcher)
.DataOptions
.perform(ViewAction)
.check(ViewAssertion);

A complete list of the available ObjectMatcher, DataOptions, ViewAction, and ViewAssertion can be found in this Espresso Cheat Sheet.

Let’s have a look at a simple example that finds a list item of type String matching the word “Espresso” and performing a click()  event on it:

onData(allOf(is(instanceOf(String.class)), ("Espresso")))
.perform(click());

The below example will find the list item having content “item content: 10” and perform a click event on its child view having id R.id.item_id:

onData(withItemContent("item content: 10"))
.onChildView(withId(R.id.item_id))
.perform(click());


Testing a RecyclerView

As RecyclerView objects behave differently than AdapterView objects, we cannot use  onData() to test RecyclerView objects.

We need to add the espresso-contrib package dependency if we want to test RecyclerView. This package includes a collection of RecyclerViewActions that we can use to scroll to positions or to perform actions on the items.

To interact with RecyclerViews using Espresso, you can use the espresso-contrib package (add the ‘com.android.support.test.espresso:espresso-contrib:2.2.2’ dependency in app/build.gradle), which has a collection of RecyclerViewActions that can be used to scroll to positions or to perform actions on items. This collection includes the following RecylerViewActions:

  • scrollTo() – This RecyclerViewActions scrolls to the matched View.
  • scrollToHolder() – This RecyclerViewActions scrolls to the matched View Holder.
  • scrollToPosition() – This RecyclerViewActions scrolls to a specific position.
  • actionOnHolderItem() – This RecyclerViewActions performs a View Action on a matched View Holder.
  • actionOnItem() – This RecyclerViewActions performs a View Action on a matched View.
  • actionOnItemAtPosition() –  This RecyclerViewActions performs a ViewAction on a view at a specific position.

Let’s look at an example of how we can test a RecyclerView:

onView(ViewMatchers.withId(R.id.rv_espresso))
.perform(
 RecyclerViewActions.actionOnItemAtPosition(10, click()));

The above example will first find the RecyclerView with id R.id.rv_espresso, then scroll to its 5th position and perform a click event on that item.

Similarly, we can use other RecyclerViewActions as well.

Now let’s see how we can test a custom RecyclerView, for example, an ExpandableRecyclerView. Let’s consider the case when we need to test some child view of the expandable group view.

Firstly, we will write a custom Action that we can perform:

public class ChildViewAction {

    public static ViewAction clickChildViewWithId(
               final int id) {
        return new ViewAction() {
            @Override
            public Matcher<View> getConstraints() {
                return null;
            }

            @Override
            public String getDescription() {
                return 
                 "Click on a child view 
                         with specified id.";
            }

            @Override
            public void perform(UiController uiController,
                                    View view) {
                View v = view.findViewById(id);
                v.performClick();
            }
        };
    }
}

Now, we will perform this action on the child view:

//Expands group at position 0 by performing click action
onView(withId(R.id.rv_espresso))
.perform(
  RecyclerViewActions.actionOnItemAtPosition(0, click()));

//Performs click action on child view at position 0
 of group 0
onView(withId(R.id.rv_espresso))
.perform(
  RecyclerViewActions.actionOnItemAtPosition(1,
  ChildViewAction.clickChildViewWithId(
                         R.id.view_child_item)));

Now let’s have a look at how we can write and use a Custom Matcher and a Custom Failure Handler.

Custom Matcher & FailureHandler

We might need to implement a Custom Matcher or a Custom Failure Handler while writing our test cases as the default ones might not suffice in some cases.

1. Custom Matcher

There might be cases when the default available Matchers are not sufficient. In this case, we might need to write our own matcher. Let’s consider a case when we need to check that the text inside an EditText matches a regular expression; since there is no default matcher that does this for us, we will need to give our own implementation for it.

Let’s see how we can write a custom matcher for validating a pattern:

public class PatternMatcher {

    static Matcher<View> withPattern(
                       final String pattern) {
        checkNotNull(pattern);

        return new BoundedMatcher<View, EditText>(
                 EditText.class) {

            @Override
            public void describeTo(
                           Description description) {
                description
                 .appendText(
                       "The EditText does not conform
                                      to the pattern: ")
                 .appendText(pattern);
            }

            @Override
            protected boolean matchesSafely(
                                  EditText item) {
                return item
                         .getText()
                         .toString()
                         .matches(pattern);
            }
        };
    }
}

Now let’s see how we can use this matcher:

onView(withId(R.id.et_email))
.check(matches(
          PatternMatcher.withPattern(
                   Patterns.EMAIL_ADDRESS.toString())));


2. Custom Failure Handler

There might be cases in which we might want to handle some Exception that Espresso throws and give our own implementation for it, like logging some extra data that might be more meaningful.

private static class CustomFailureHandler
                 implements FailureHandler {
  private final FailureHandler delegate;

  public CustomFailureHandler(Context targetContext) {
    delegate = new DefaultFailureHandler(targetContext);
  }

  @Override
  public void handle(Throwable error,
                       Matcher<View> viewMatcher) {
    try {
      delegate.handle(error, viewMatcher);
    } catch (NoMatchingViewException e) {
      throw new MySpecialException(e);
    }
  }
}

Now that we have written a custom failure handler, let’s see how we can set it up.

@Override
public void setUp() throws Exception {
  super.setUp();
  getActivity();
  setFailureHandler(
       new CustomFailureHandler(
               getInstrumentation().getTargetContext()));
}

This was all about how to write basic test cases for different components. I hope this blog motivates you to write test cases for your applications. At first, it might be difficult, but once you get a hang of it, it will be of great use.

You can refer to this link for more on Espresso for Android UI Testing.

Hope you enjoyed reading.

Topics:
android ,ui testing ,espresso ,mobile ,mobile testing

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}