Custom Espresso ViewAction

While working on a project for a client the design team had a pretty specific requirement for how EditText need to appear. I tried to bend TextInputEditText and TextInputLayout to my will but it just wasn't happening. This lead me to create a custom text input view which could show errors, hints, supporting labels, and suffixes in exactly the right way. I even ended up subclassing EditText to get some of these features.

Unfortunately this makes writing Espresso tests a little harder, leading to some very long and ugly onView(...) and failing replaceText(...) calls. By diving into ViewActions I was able to make a few modifications to smooth over this whole situation.

I started with ReplaceTextAction, copying the source into my own ReplaceTextInputViewAction since my class is called (TextInputView.) There are really on two important methods here - getConstraints() and perform()getConstraints() simply sets the requirements for the ViewAction to proceed, otherwise throwing an exception. perform() is where it actually does the action. This is where it can get exciting if you have a complicated custom view, but its important to have a nice clean interface to get/set the state of your View. Here is what I ended up with:

/**
 * Replaces view text by setting {@link TextInputView}s text property to given String.
 */
public final class ReplaceTextInputAction implements ViewAction {
    private final String stringToBeSet;

    public ReplaceTextInputAction(String value) {
        checkNotNull(value);
        this.stringToBeSet = value;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Matcher getConstraints() {
        return allOf(isDisplayed(), isAssignableFrom(TextInputView.class));
    }

    @Override
    public void perform(UiController uiController, View view) {
        ((TextInputView) view).setText(stringToBeSet);
    }

    @Override
    public String getDescription() {
        return "replace text";
    }
}

I also created static helper methods to mimic what ViewActions is doing.

public class CustomViewActions {
    /**
     * Returns an action that updates the text attribute of a view.
     * View preconditions:
     * - must be displayed on screen
     * -must be assignable from TextInputView
     */
    public static ViewAction replaceTextInput(@Nonnull String stringToBeSet) {
        return actionWithAssertions(new ReplaceTextInputAction(stringToBeSet));
    }
}

This leads to not only a working test, but a nice and easy way to write tests for these custom Views.

onView(withId(R.id.email))
    .perform(replaceTextInput("foo@bar.com"));