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
Inline coding questions will not be answsered. Instead, ask on StackOverflow and put the link in the comment.
Very useful post! Thank you!
ReplyDeleteJeez thanks! This is the first, actually helpful, post I found.
ReplyDeleteYou're an Espresso wizard!
ReplyDeleteTHANK you for this great post!
ReplyDeletethanks, very well explained
ReplyDeleteawsome sample!
ReplyDeleteThank you for the short and clean description and also for the example!
ReplyDeleteYour 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.
ReplyDeleteUpdate: right tool for waiting for async callbacks when you are not testing UI is CountdownLatch from java.util.concurrent.
Delete