Update: A new (better) version of this post is available on the Jimdo Dev Blog.
Recently I invested a decent amount of time in making our functional tests less clunky, especially when there are async computations involved. We started using Espresso a few days after it was released and never looked back. In this blog post I’d like to focus on how you can tell Espresso to wait for an async computation to finish before performing any actions on a View
, and a few gotchas I learned. Espresso introduces the concept of IdlingResource
, a simple interface that
Represents a resource of an application under test which can cause asynchronous background work to happen during test execution
The interface defines three methods:
getName()
: must return a non-null string that identifies an idling resource. Morover, as the docs state:
it is used for logging and idempotency of registration
isIdleNow()
: returns the current idle state of the idling resource. If it returnstrue
, theonTransitionToIdle()
method on the registeredResourceCallback
must have been previously called.registerIdleTransitionCallback(IdlingResource.ResourceCallback callback)
: normally this method is used to store a reference to the callback to notify it of a change in the idle state.
Idling resource registration
Registering an idling resource is really simple: just call Espresso.registerIdlingResource(myIdlingResource)
. This call is idempotent, meaning that > it can be applied multiple times without changing the result beyond the initial application.
This way consequent calls to Espresso.registerIdlingResource(myIdlingResource)
for an idling resource with the same name won’t have any effect (Espresso will simply log a warning). Generally this is no big deal, but it becomes an issue if an idling resource has a dependency to the current Context
. For example, the application under test can have a WebView
and the tests need to wait for a page to be fully loaded. If idempotence is not taken into account and an idling resource with a reference to a WebView
instance is registered - for example in the setUp()
method of a test class - bad things will happen. First, subsequent tests will rely on a wrong referenced component in idling resource to be checked and will probably fail, and second the first Context
is leaked since we’re holding a strong reference to it. The solution to that is to have an ActivityLifecycleIdlingResource
and inject and clear the reference to a component when appropriate.
abstract class ActivityLifecycleIdlingResource<T> implements IdlingResource {
private T component;
void inject(T component) {
this.component = component;
}
void clear() {
this.component = null;
}
}
Another - probably less error-prone - solution would be to have an Espresso.unregisterIdlingResource(myIdlingResource)
API, there is already a feature request to add it. As for registering idling resources that are needed in all tests, I ended up registering them in the callApplicationOnCreate(app)
method of a custom InstrumentationTestRunner
, this way I am sure the registration happens only once.
Implementing an idling resource for a thread pool executor
There can be multiple reasons why you’d want your application to not use the built-in Android components that handle async operations, in this case you’d need to define an idling resource that checks if the executor(s) used by the application are idle. Looking at the Espresso source code, with a small refactoring to the AsyncTaskPoolMonitor
class (Espresso uses it to check if there is some tasks running on the AsyncTask
thread pool executor) a general ThreadPoolIdlingResource
can be implemented.