Advanced Android Espresso

Building a Grid Layout With RecyclerView and Realm

Building an app with RecyclerView? Realm Java is an easy way to power a list or grid, and supports any type of data you can design. See how simple it is!

Espresso is a very powerful UI testing framework for Android. Chiu-Ki Chan describes several techniques to make the most of it, running through: combining matchers (withParent, isAssignableFrom) to pinpoint your view; onData and RecyclerViewActions to test ListView and RecyclerView; Custom ViewAction and ViewAssertion; and using Dagger and Mockito to write repeatable tests.


What is Espresso? (00:28)

Espresso is automatic UI testing (“no hands testing!”). Once you write the code, Espresso will execute using events: do various things that as a normal user you will do, then verify the test (different from JUnit testing where there is no UI element). In Espresso you can see what is happening.

It is a first-party library (Google wrote it under the Android testing support library). It simulates User interactions (clicking, typing), and it does automatic synchronization of test action with app UI. Espresso will watch the UI thread (the thread that renders all the pixels on screen). Before Espresso, I used ActivityInstrumentationTestCase2: my test would fail because the button was not drawn yet and I was trying to click on it. You put in some Sleep statements hoping that after Sleeping the pixels will be drawn and then you can click on it. With the automatic synchronization Espresso will loop the Sleep until the UI thread is idle then it will verify and click things. You do not need to Sleep.

Hello World (02:56)

Espresso has a fluent API (can tack things one after another). The first thing: locate the view that you want to act on. Espresso has the concept of ViewMatchers. Once you locate the view then you can perform actions to it (typing, clicking, swiping). You can check using ViewAssertions.

Formula

Basic Espresso test: The Hello World app. It has a greet button - when you click on it it will disable it. Using this formula we first need to locate this view:

onView(ViewMatcher)
  .perform(ViewAction)
  .check(ViewAssertion);

ViewMatcher

Espresso comes with a ViewMatcher - withId. In our case we want to look for the view with the R.id.greet_button. We will use the ViewAction click() to click on it. Then, we want to assert that the greet button is no longer enabled.

onView(withId(R.id.greet_button))
  .perform(ViewAction)
  .check(ViewAssertion);

ViewAction

onView(withId(R.id.greet_button))
  .perform(click())
  .check(ViewAssertion);

In this ViewAssertion (see below), instead of it is not enabled, we are doing it is enabled and then negating it. You can combine different Matchers and Assertions to do exactly what you need.

ViewAssertion

onView(withId(R.id.greet_button))
  .perform(click())
  .check(matches(not(isEnabled())));

More info:

Espresso library (05:03)

Highlighted in yellow (withId, click, matches, isEnabled): comes within Espresso. They are Android-specific:

onView(withId(R.id.greet_button))
  .perform(click())
  .check(matches(not(isEnabled())));

Hamcrest library (06:00)

There is also not, from the Hamcrest library (Java library). It has different logic startWith, endWith, if we are dealing with strings and not and allOf. Together with the Espresso patch (Android-specific) and the Java pod you can do very powerful expressions.

onView(withId(R.id.greet_button))
    .perform(click())
    .check(matches(not(isEnabled())));

More Info:

Combining Matchers (06:17)

Let’s say that you wrote this app and you want to verify the Toolbar title is displaying the correct string.

Your layout does not contain that view (you do not have the ID). To find this view, we will use Hierarchy Viewer (examines the hierarchy of your app). You have a root node: all the different views hang off it and it shows you the structure (on the screen, not just the path that you set with setContentView).

And the TextView is the one that has the “My Awesome Title” string (the one that you want to verify). We have located the view that we want to match. Bad news: There is no ID. But using all the different Matchers, I know that this TextView has a parent (Toolbar). It can do a combination - I will find a view that has two conditions isAssignableFrom: 1) TextView (I am a TextView, I want to find a view that is a TextView), 2) Toolbar (find that one TextView that is under a Toolbar). The reason why we want to do that with two conditions (instead of just the TextView one) is that there can be multiple TextViews. To pinpoint that one particular view I will use allOf Matcher. Then I wrap it into a helper function; every time I want to match a Toolbar I can just call Match Toolbar and then pass a string.

@Test public void toolbarTitle() {
  CharSequence title = InstrumentationRegistry
    .getTargetContext().getString(R.string.my_title);
  matchToolbarTitle(title);
}
private static ViewInteraction matchToolbarTitle(
    CharSequence title) {
  return onView(
    allOf(
        isAssignableFrom(TextView.class))), withParent(isAssignableFrom(Toolbar.class))
    .check(matches(withText(title.toString())));
}

Custom Matchers (09:07)

You can write a custom Matcher. The previous example works, but it is very fragile (Toolbar class: today, it contains the TextView as the direct child of a Toolbar; tomorrow, they may want to wrap it inside CoordinatorLayout because is not a part of public API).

The Toolbar class has a function toolbar.getTitle() which, since it is in the public API, it is going to be stable. Instead of trying to pinpoint the TextView, the ViewMatcher takes on a view that is assignable from the Toolbar class. We are matching on the Toolbar class and we are going to verify that it has the Toolbar title (which is a custom Matcher).

private static ViewInteraction matchToolbarTitle(
    CharSequence title) {
  return onView(isAssignableFrom(Toolbar.class))
      .check(matches(withToolbarTitle(is(title))));
}

With Toolbar title generates a custom Matcher. The BoundedMatcher only works on the Toolbar. We have access to the Toolbar functions. In Match Safely function we wanted to use the getTitle out of the Toolbar and matches the text that we give it. Instead of passing a string you want to match, you are passing a TextMatcher. This way you can use all the Hamcrest test Matchers (such as equalTo), which is your plain old comparison. Y

You can do Prefix Matching, Suffix Matching or Contain String (if you just want the string to be anywhere).

If you want to use a function that is not a part of Espresso you can write your own Matcher.

private static Matcher<Object> withToolbarTitle(
    final Matcher<CharSequence> textMatcher) {
  return new BoundedMatcher<Object, Toolbar>(Toolbar.class) {
    @Override public boolean matchesSafely(Toolbar toolbar) {
      return textMatcher.matches(toolbar.getTitle());
    }
    @Override public void describeTo(Description description) {
      description.appendText("with toolbar title: ");
      textMatcher.describeTo(description);
    }
  };
}

More info:

onData (11:42)

ListView operates on a different way. Instead of looking for a view, you are looking for a particular object in the Adapter that is supporting the ListView. That is why your onData takes an object Matcher. You have the same Perform, View Action, Check, View Assertion. And on top, Data options. You can have multiple ListViews. You can pinpoint it in with inAdapterview or you can look inside the item and match on the particular child view.

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

I have a ListView and it has a list of numbers. I want to run a test to verify that item 27 stays 27. I am looking for the item that stays 27, it will scroll and look for it. The basic setup of the app: I have an Adapter (backed by an array of integers), and I will set it as the data for this ListView.

final Item[] items = new Item[COUNT];
for (int i = 0; i < COUNT; ++i) {
  items[i] = new Item(i);
}
ArrayAdapter<Item> adapter
  = new ArrayAdapter<>(this,
      android.R.layout.simple_list_item_1,
      items);
listView.setAdapter(adapter);

The item is a wrapper around a simple integer that has a two string function (the ArrayAdapter knows what to display).

public static class Item {
  private final int value;
  public Item(int value) {
    this.value = value;
  }
  public String toString() {
    return String.valueOf(value);
  }
}

Also, when you click on an item the footer TextView it will display that value. We also want to verify that.

listView.setOnItemClickListener(
    new AdapterView
        .OnItemClickListener() {
    public void onItemClick(
        AdapterView<?> parent,
        View view, int position, long id) {
      textView.setText(
        items[position].toString());
      textView.setVisibility(View.VISIBLE);
    }
});
listView.setOnItemClickListener(
    new AdapterView
        .OnItemClickListener() {
    public void onItemClick(
        AdapterView<?> parent,
        View view, int position, long id) {
      textView.setText(
        items[position].toString());
      textView.setVisibility(View.VISIBLE);
    }
});

We are going to verify that the bottom TextView is not displayed. I said, withID this text and I want to match it as not displayed. I am going to use the onData function to look for the item with value 27 inside the AdapterView that has the ID list. Once I find it I will click on it. After I click on it, I will then verify that the bottom TextView is containing the text 27 and also it is also displayed.

You have the onView with text ID. And I have performed two checks. Rather than looking for the view again, I can chain them. It will check for the first condition (it has the text 27), and then it will check that it is displayed. You can keep stacking on things, you can perform actions and check, and then perform action. It does not need to go look for the view again. You already have the view in your hand.

@Test
public void clickItem() {
  onView(withId(R.id.text))
      .check(matches(not(isDisplayed())));
  onData(withValue(27))
      .inAdapterView(withId(R.id.list))
      .perform(click());
  onView(withId(R.id.text))
      .check(matches(withText("27")))
      .check(matches(isDisplayed()));
}

withValue is a custom Matcher that I wrote. BoundedMatcher takes in an integer and in the Match Safely function it compares to the value of the item using strings. It is a string comparison.

public static Matcher<Object> withValue(final int value) {
  return new BoundedMatcher<Object,
      MainActivity.Item>(MainActivity.Item.class) {
    @Override public void describeTo(Description description) {
      description.appendText("has value " + value);
    }
    @Override public boolean matchesSafely(
        MainActivity.Item item) {
      return item.toString().equals(String.valueOf(value));
    }
  };
}

We cannot use onData: RecyclerView is a ViewGroup (not AdapterView). The onData operator only works on AdapterView - it is not just ListView, you can use it with GridView as well. We are going back to using the onView View Matchers.

We will use that onView and perform check formula. To click on item is different. We will do the same pre-condition checking that the text is not displayed, but to click on that particular list item we are going to say that onView with ID RecyclerView. We are operating on that RecyclerView level, perform, and then we will use RecyclerViewActions (from the Espresso library). We will have a specific action (actionOnItemAtPosition(27)). You look for position item 27 and perform the action click on it.

@Test public void clickItem() {
  onView(withId(R.id.text))
    .check(matches(not(isDisplayed())));
  onView(withId(R.id.recycler_view))
    .perform(
      RecyclerViewActions.actionOnItemAtPosition(27, click()));
  onView(withId(R.id.text))
    .check(matches(withText("27")))
    .check(matches(isDisplayed()));
}

actionOnItemAtPosition is very different (see code below): we are no longer doing Data Matching and we need to look for the position and then perform it all at once in this one giant View Action in it. The rest of the code is the same: we will continue to verify that it displays 27 and it is not hidden. This replaced the middle line (onView).

Two ways of thinking, one the AdapterView of looking for things focuses on the data, but if you are doing RecyclerView then you have to go back and focus on the view again. You find the RecyclerView and act on it with the RecyclerViewActions. There are a couple of other ones. If you do not want to look for an at position you can also look for the View Holder which then you have a Matcher that is looking for a specific ViewHolder or you can also do the action item with a ViewMatcher. Make sure that you know they are different.

// ListView
onData(withValue(27))
    .inAdapterView(withId(R.id.list))
    .perform(click());

More information:

Idling Resource (19:43)

Espresso has this notion of the UI queue being idle - no more UI events handled, are queued. That includes clicks, rendering. It also has the notion of idling: the AsyncTask pool is empty. But beyond that it does not know how to wait for your app to be ready to go. Espresso provides a nice framework for you to write your own idling conditions (Idling Resources).

Example: Wait until an IntentService is not running (IntentServiceIdlingResource). Whenever you write an Item Resource you need to Override three functions: getName, registerIdleTransitionCallback, and isIdleNow. Make sure that you have a unique name (e.g. class name) because you are going to be registering this Idling Resource later, and you use the name as the key. Then register the IdleTransitionCallback you stash that callback into a particular number variable so that in isIdleNow now you can call it when you determine that whatever that you are waiting for is done. You can tell Espresso, “I am idle, go ahead”. In our particular case we are going to define idle as the IntentService is not running (isIntentServiceRunning()).

@Override public String getName() {
  return IntentServiceIdlingResource.class.getName();
}

@Override public void registerIdleTransitionCallback(
    ResourceCallback resourceCallback) {
  this.resourceCallback = resourceCallback;
}

@Override public boolean isIdleNow() {
  boolean idle = !isIntentServiceRunning();
  if (idle && resourceCallback != null) {
    resourceCallback.onTransitionToIdle();
  }
  return idle;
}

Then I have a callback, I will call it and it will return the boolean. I am going to query the ActivityManager for the specific name (IntentService). If it is there then it is running, I will return true; if I could not find it then I return false.

private boolean isIntentServiceRunning() {
  ActivityManager manager =
    (ActivityManager) context.getSystemService(
      Context.ACTIVITY_SERVICE);
  for (ActivityManager.RunningServiceInfo info :
          manager.getRunningServices(Integer.MAX_VALUE)) {
    if (RepeatService.class.getName().equals(
          info.service.getClassName())) {
      return true;
    }
  }
  return false;
}

After you have defined your custom Idling resource, you need to register it (most of the time this is JUnit4 syntax). I have an annotation that is before and after - you can register it before your tests run. And then after your test is run unregister it.

@Before
public void registerIntentServiceIdlingResource() {
  idlingResource = new IntentServiceIdlingResource(
    InstrumentationRegistry.getTargetContext());
  Espresso.registerIdlingResources(idlingResource);
}

@After
public void unregisterIntentServiceIdlingResource() {
  Espresso.unregisterIdlingResources(idlingResource);
}

More info:

Dagger and Mockito (31:03)

Dagger is a framework for Dependency injection: you have a central repository of classes. We are trying to make tests work: we are doing to use Dagger to provide different objects for app and test; we will use Mockito to mock objects in test.

In Dagger2 you are going to define a Component (a collection of different modules that can provide classes to your app and your test). In our case we are going to do something with the clock. It is a classic example because if your app depends on the current time then you cannot verify anything because the current time changes. In the ApplicationComponent we are going to have a real clock module (provides the actual current time) and in the test we are going to have a module that is mocked (using annotation). You have the app’s Component and then you have the list of Modules. Dagger is going to then go ahead and do co-generation for you at compile time. You can then call these functions in your app.

In sum, you have an ApplicationComponent that provides a clock module and then you also have a TestComponent that provides a mock clock module. The ApplicationComponent only injects into the main activity, it is not aware of the existence of tests. The TestComponent need to inject to both and they are singletons. If you are providing this particular object and then you are changing it in your test the app is going to use the exact same object so that it gets the same value.

public interface DemoComponent {
  void inject(MainActivity mainActivity);
}

@Singleton @Component(modules = ClockModule.class)
public interface ApplicationComponent extends DemoComponent {
}

@Singleton @Component(modules = MockClockModule.class)
public interface TestComponent extends DemoComponent {
  void inject(MainActivityTest mainActivityTest);
}

In your application, when you are onCreate you are creating the application. DaggerDemoApplication_ApplicationComponent is the auto-generated class. It will go through all your annotations, which will allow you to create a component (collection of modules).

private DemoComponent component = null;

@Override public void onCreate() {
  super.onCreate();
  if (component == null) {
    component = DaggerDemoApplication_ApplicationComponent
        .builder()
        .clockModule(new ClockModule())
        .build();
  }
}

In your test you are going to be able to use the setComponent function which is defined on the activity, which will provide a different set of modules.

setComponent
public void setComponent(DemoComponent component) {
  this.component = component;
}

public DemoComponent component() {
  return component;
}

setComponent(testComponent) in test

With Mockito you can control time. In Joba-Time (a pretty popular dateTime libray for Java) dateTime will give you the current day and time. In your test Mockito allows you to say when someone calls the function getNow on my mock clock return this particular point in time. When you run your test you will be able to do something like this. Maybe you have a TextView that displays today’s date. You can verify that it will display the string 2008-09-23 because that is the time the clock is going to provide. Without this mocking, you run the test today you return this value, you run the test tomorrow you return this other value. This is the power of combining Mockito and Dagger and Espresso so that they all work together. You can write test that pretends to be a human, but also in a predictable environment so that you can keep running the test and it will pass unless you wrote a bug in your future code.

/* App */
public DateTime getNow() {
  return new DateTime();
}
/* Test */
Mockito.when(clock.getNow())
  .thenReturn(new DateTime(2008, 9, 23, 0, 0, 0));
/* Espresso */
onView(withId(R.id.date))
  .check(matches(withText("2008-09-23")));

More info - Blog post abotu Mockito - Example repo - Example using the Shared Preferences

Summary (29:06)

Running through the basics (ViewMatcher, ViewAction, ViewAssertion) I showed you how to match the Toolbar using two different ways (combining Matchers, writing your own custom Matcher). I went through ListView and RecyclerView, and how they differ. Spent some time on IdlingResource, and Dagger and Mockito to get you to the next level.

Friend Spell (29:33)

I have open souced a complete app. It uses: the Google Plus API, the Nearby API and the Database. At first glance it is really hard to test. It asks you to log into Google Plus. I have to set up a device that has a certain Google Plus account and then I have to log in; next thing it is going to use the Nearby API. It will spell a word depending on the name of the person that you are Google Plus profile logged in as. I used the same technique that I described, Dagger, Mockito to mock stuff. Mockito is more advanced because I need to capture the Google sign in with the listener and then replay it right back. It also has other testing. I have some models, POJO’s, I set some function then I verify the conditions. I also have UI-less Instrumentation (to test the Database). I am not executing the UI, but I also need context because it is still going through Android classes. And, of course, it also has Espresso.

Thank you! Resources & Q&A (32:28)

More links:


Q: For the combined Joda-Time and, Mockito and Espresso example, where did you initialize Joda-Time? Did you rely on initializing it with the application?

Chiu-Ki: I just call new dateTime.

Q: You have to initialize it with a context at some point early in your application lifecycle, and it is suggested that you do it in application onCreate. But I know that we have also been talking about separating test environments so that your, your context is perhaps different than it would be in the normal run time. I am wondering do you have to do anything special to initialize Joda-Time in the test context?

Chiu-Ki: I do not and so far nothing blew up, but maybe some other library needs the context. It is not specific to Joda-Time. Currently this set-up only has the clock module component. You will have an additional module that is an Android module which is capable of providing a context. Then in your clock module in the constructor it will take in a context which will be provided by the Android module. And Dagger is smart enough to just do essentially Class Matching. And then the Android module raises its hand, I know how to give you a context. And then it will provide that. And then within the clock module, you will be able to use that. Once you have that context in the constructor of your clock module then you can come back here to the getNow function inside your clock module and then use the context there.

Q: Question specific to the Dagger2 example that you showed. The difficulty in Dagger2 is really, there is no module override available in Dagger2. I wanted to know what would be your recommend workaround for being able to override specific dependencies inside of your mock modules without having to completely rewrite, the entire module. You want to have code duplication on the test classes and also on the application. Your example only has the clock module which provides only one dependency, but if you have, 50 dependencies and you only need to override just the one. You would not want to create a test module with 49 real classes and only one mock. What would be the best workaround?

Chiu-Ki: It has been a pain point for Dagger2, mostly because it is available in Dagger1! Why did you take that away from us? I have not personally encountered that just because the way I set up my modules, but I have heard people, what they have done is that, you have your app source and then your test source. you can have another source that is the common source which contains the modules that are shared between the test and the app and then when you make your component that is for the app you import those, you include those. And then the test also includes the same ones. And then you only implement each thing that is different once. I heard people do that.

Q: I ran into one issue I exactly used the same set-up where I had one class, I had a simple UI which was just fetching a list of GitHub issues at the click of a button and the dependency, the API dependency, was being injected by Dagger and I replaced it by a mock inside one of my modules. I only wanted to verify interaction with that mock once the button’s clicked. But because that mock did not return any action that would affect the UI thread, then it was hanging forever. I want to verify interaction with a mock, but I do not want to have to code up an action which is going to affect the UI thread. I just want to verify interaction with a mock. Is there a workaround for things like this?

Chiu-Ki: It is a network call, except that you are mocking the result; Is that triggered by a button click or any user action? When does the fetching start?

Q: You click the button, it talks to that injected mock. Then that injected mock, you are passing a callback method which will just return a list of issues which will populate an adapter, for example.

Chiu-Ki: in that case I would expect it to, instead of hang, go too fast. It did not wait until the mock to come back and was starting to verify things. I was not sure why it was behaving that way. I am not able to diagnose why that happened because that is not the expected behavior that I would be.

Q: The fix for me was to take that mock and just use Mockito to manually call the callback that the activity was passing.

Chiu-Ki: Espresso testing at that point. You do not really need to, because then nobody is clicking anything.

Q: The UI’s wired up properly with the back end. You click a button and you want to make sure that when Espresso clicks this button then there is interaction with a mock. Meaning that clicking this button effectively tried to do the API call, you do not care about what the API call returned, but you want to verify that there is some interaction with that mock.

Chiu-Ki: Write a blog post, Twitter to me and I will take a look because I feel like there is more to what we can describe.

Q: I find one of my major pain points is coming up with complex Macthers for hierarchies that are much more complex than what you showed. I was wondering if you have any general suggestions in finding those complex Matchers perhaps with multiple parents and multiple siblings in a way that will not be fragile.

Chiu-Ki: My recommendation would be to simplify your View Hierarchy. Wrap things in the CustomView so that semantically they make more sense. The reason why you have all these things laid out is because probably when you are looking at the UI they are not individual buttons and individual tacks. They are probably logical units. I think it will help not just testing, but your actual app and development if you can group them in logical units. Then at that point then you can hide the internal logic and say, this manual item thing has six things inside, but then I can get them with programmatic calls like the getTitle one.

Q: One hardest problems is when elements repeat themselves, such as if you are using a ListView and each item of the ListView has several buttons. It can be difficult to find a container maybe with the text you are looking for and then go back down the chain after that to get to the buttons within the hierarchy. Is there anything that would help sort of expose the chain to get to a path?

Chiu-Ki: You can see who is a child of whom; that helps you to expose the structure. One thing that you could do is a helper function. You encapsulate that, every time you are trying to mesh this particular way of setting up you can at least repeat it. I basically smell bad code when you tell me that your view explodes with too many children and too many level deep and it is really difficult to find things. Without going through your specific example it is just really hard to give general advice.


Chiu-Ki Chan

Chiu-Ki Chan

Chiu-Ki is an Android developer with a passion for speaking and teaching. She has spoken at numerous conferences all over the world, and has been recognized as a Google Developer Expert for her extensive knowledge of Android. She runs her own mobile development company, producing delightful apps such as Monkey Write, Heart Collage, and Fit Cat.