Saturday, April 25, 2015

Espresso: Custom Idling Resource

One key feature in Espresso is the synchronization between the test operations and the application being tested. This is built around the concept of idling: Espresso waits until the app is "idle" before performing the next action and checking the next assertion.

Idle

What does it mean for the app to be idle? Espresso checks for several conditions:

  • There is no UI events in the current message queue
  • There is no tasks in the default AsyncTask thread pool

This takes care of waiting for the UI rendering and AsyncTask completion. However, if your app performs long-running operations in other ways, Espresso will not know how to wait for those operations to finish. If that is the case, you can tell Espresso to wait by writing a custom IdlingResource.

IntentServiceIdlingResource

Say you use an IntentService to do some long computation, and return the result to your activity via broadcast. We want Espresso to wait until the result is returned before checking that it was displayed correctly.

To implement an IdlingResource, you need to override 3 functions: getName(), registerIdleTransitionCallback() and isIdleNow().

@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;
}

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;
}

The idle logic is implemented in isIdleNow(). In our case, we check if our IntentService is running by querying the ActivityManager. If the IntentService is not running, we can inform Espresso calling resourceCallback.onTransitionToIdle().

Register your idling resource

You need to register your custom idling resource in order for Espresso to wait for it. Do it in a @Before method in your test, and unregister it in @After.

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

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

Full example

Check out the source code for a full example. Try commenting out the IdlingResource registration and watch the test fail.

Source code: https://github.com/chiuki/espresso-samples/ under idling-resource-intent-service

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

9 comments:

Inline coding questions will not be answsered. Instead, ask on StackOverflow and put the link in the comment.

  1. Jeez thanks! This is the first, actually helpful, post I found.

    ReplyDelete
  2. Thank you for the short and clean description and also for the example!

    ReplyDelete
  3. Your posts and speeches are great! I just want to leave a note for others, since it was not clear to me at first: idling resource works only for Espresso asserts. If you make plain junit asserts they won't wait for the resource to be idle (obvious to me now...). I wanted to test some backend calls with Firebase Database and I didn't need an activity for that, but I still needed to wait for the callback... I ended up making a fake check on a text view of a fake activity. If you have some advices on this one they are very welcome.

    ReplyDelete
    Replies
    1. Update: right tool for waiting for async callbacks when you are not testing UI is CountdownLatch from java.util.concurrent.

      Delete