Everything about Idling Resources in Espresso

Android idling resources in espresso

14 March 2023 Stephan Petzl Leave a comment Tech-Help

Idling resources are a way to tell Espresso to wait till the background process has finished. In this article, we look at how idling resources work. Also, we solve some common problems of Espresso idling resources.

Why do we need idling resources?

When Espresso tests are executed by the test runner, the timing of actions is crucial. If the test runner tries to press a button before the button is shown or active, it might trigger an error unintentionally. So the straightforward way to fix this issue is to add a lot of delay (Thread.sleep()) commands into your code. The downside is: We are wasting precious time which can accumulate to minutes and even hours for bigger test batches.Unfortunately, Espresso is not aware of background tasks (such as loading web content, playing an animation, background processing) and therefore can’t know when exactly to trigger the next action.So Idling Resources are a way to tell Espresso to wait till the background process has finished.While there are objections about using idling resources, currently it is the standard way to go about it.

How do idling resources work in Espresso?

Idling Resources is an object that communicates to Espresso when the app becomes idling. Espresso always waits for an app to become idle before executing the next interaction. This reduces test flakiness, which is a big deal in software testing.The idling resource contains a logical condition that must be fulfilled for the application to reach the inactive state. After creating an idling resource object, it must be registered with IdlingResourceRegistry to be noticed by Espresso. From Espresso’s point of view, IdlingResource is a representation of the dynamic resource of the Android application under test. It can be any operation performed by the application, for example REST requests, database operations, animations, loading data into the list and more.There are predefined idling resource classes that can be used in most cases.

Predefined idling resources in Espresso:

CountingIdlingResource keeps track of all open tasks via an internal counter variable. The related resource is thought to be inactive when the counter is 0. Its capability resembles a semaphore very much. The majority of the time, this method is adequate for controlling the asynchronous work of your app during testing.UriIdlingResource Similar to CountingIdlingResource, however the resource cannot be declared idle until the counter has been 0 for a predetermined amount of time. This extra delay accounts for successive network requests, when an app in your thread could submit a new request just after getting a response to an earlier request.IdlingThreadPoolExecutor a unique ThreadPoolExecutor implementation that maintains tabs on the overall number of active tasks across all the thread pools that have been formed. The number of running jobs is kept up to date by this class using a CountingIdlingResource.IdlingScheduledThreadPoolExecutor a unique ScheduledThreadPoolExecutor implementation. It has the same features and abilities as the IdlingThreadPoolExecutor class, but it also has the ability to track tasks that are regularly or in the future planned to run.

How to use idling resources?

We need to configure Espresso according to the documentation. IdlingResource is not included in the base library, it is an extension.
dependencies {
    // Core library
    androidTestImplementation "androidx.test:core:$androidXTestVersion0"

    // AndroidJUnitRunner and JUnit Rules
    androidTestImplementation "androidx.test:runner:$testRunnerVersion"
    androidTestImplementation "androidx.test:rules:$testRulesVersion"

    // Assertions
    androidTestImplementation "androidx.test.ext:junit:$testJunitVersion"
    androidTestImplementation "androidx.test.ext:truth:$truthVersion"

    // Espresso dependencies
    androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
    androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
    androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
    androidTestImplementation "androidx.test.espresso:espresso-accessibility:$espressoVersion"
    androidTestImplementation "androidx.test.espresso:espresso-web:$espressoVersion"
    androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$espressoVersion"

    // The following Espresso dependency can be either "implementation",
    // or "androidTestImplementation", depending on whether you want the
    // dependency to appear on your APK’"s compile classpath or the test APK
    // classpath.
    androidTestImplementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion"
}
After that you can simply create an instance of one of the predefined idling resource types:
CountingIdlingResource countingResource = new CountingIdlingResource("FooServerCalls");
Since you need to access this class from within our application code, you need to place it inside of your src/main/java directory and NOT inside of src/androidTest/java. This is one of the disadvantages of idling resources.The countingResource instance can then be passed to whatever place in your code where things happen outside your main thread. Here is an example implementation:
public interface FileSystem {
  public File newFile(String name);
  public void deleteFile(File file);
}

public DecoratedFileSystem implements FileSystem {
  private final FileSystem realFileSystem;
  private final CountingIdlingResource fileSystemIdlingResource;

public DecoratedFileSystem(FileSystem realFileSystem,
  CountingIdlingResource fileSystemIdlingResource) {
  this.realFileSystem = checkNotNull(realFileSystem);
  this.fileSystemIdlingResource =  checkNotNull(fileSystemIdlingResource);
}

  public File newFile(String name) {
    fileSystemIdlingResource.increment();
    try {
      return realFileSystem.newFile(name);
    } finally {
      fileSystemIdlingResource.decrement();
    }
  }

  public void deleteFile(File file) {
    fileSystemIdlingResource.increment();
    try {
      realFileSystem.deleteFile(file);
    } finally {
      fileSystemIdlingResource.decrement();
    }
  }
}
Next we take a look how we could use a singleton to make the idling resource accessible from everywhere in your application.

Using a singleton class to access your idling resource

Let’s create a static class, and access our idling resource from everywhere. This is how you could provide a countingIdlingResource to the rest of your application:
// CountingIdlingResourceSingleton.kt:
import androidx.test.espresso.idling.CountingIdlingResource

object CountingIdlingResourceSingleton {

    private const val RESOURCE = "GLOBAL"

    @JvmField val countingIdlingResource = CountingIdlingResource(RESOURCE)

    fun increment() {
        countingIdlingResource.increment()
    }

    fun decrement() {
        if (!countingIdlingResource.isIdleNow) {
            countingIdlingResource.decrement()
        }
    }
}
This is how you could access the idling resource from within your MainActivity.
// MainActivity.kt:

start_activity_button.setOnClickListener {
    val intent = Intent(context, LoginActivity::class.java)

    CountingIdlingResourceSingleton.increment()
    // again we use a kotlin coroutine to simulate a 3 second network request:
    val job = GlobalScope.launch {
        // our network call starts
        delay(3000)
    }
    job.invokeOnCompletion {
        // our network call ended!
        CountingIdlingResourceSingleton.decrement()
        startActivity(intent)

    }
}

How to use the idling resource inside your test

The missing piece is to register the idling resource with the espresso test runner. This can observe our count variable and wait accordingly during execution of our test:
// LoginTest.kt: 
@Before
fun registerIdlingResource() {
    IdlingRegistry.getInstance().register(CountingIdlingResourceSingleton.countingIdlingResource)
}

@After
fun unregisterIdlingResource() {
    IdlingRegistry.getInstance().unregister(CountingIdlingResourceSingleton.countingIdlingResource)
}

How to implement a new idling resource in Espresso?

There might be cases where you need special handling of your idle state. Espresso allows you to just implement the provided IdlingResource interface, which is easy to do!
  1. We need to configure Espresso according to the documentation. The base library does not include IdlingResource, it is an extension (see above)
  2. We need to create the class that implements the IdlingResource interface.
  3. The IdlingResource interface contains three abstract methods whose implementation must be provided for IdlingResource to function properly: getName(), isIdleNow() and registerIdleTransitionCallback(ResourceCallback callback).
  4. The getName() method cannot be null and can return any string. IdlingResourceRegistry uses it to make sure that each IdlingResource is unique.
  5. We create a local ResourceCallback field in IdlingResource and replace registerIdleTransitionCallback(ResourceCallback callback). IdlingResourceRegistry uses this method when registering the IdlingResource. IdlingResourceRegistry will send an instance of ResourceCallback to the IdlingResource, which will connect it to the entire application. This ResourceCallback received must be saved in the local field previously created.
  6. The isIdleNow() method is the kernel and the most crucial part of IdlingResource. It gives IdlingResourceRegistry information about the current state of the condition indicated in IdlingResource. It must contain a logical expression that changes the value from false to true, indicating that the asynchronous operation has ended now.

Idling resources and RxJava

There is a ready-to-use IdlingResource for Espresso which wraps the RxJava Scheduler: Check out the repository here: https://github.com/square/RxIdlerExample:
RxJavaPlugins.setInitComputationSchedulerHandler(Rx3Idler.create("RxJava 3.x Computation Scheduler"));
RxJavaPlugins.setInitIoSchedulerHandler(Rx3Idler.create("RxJava 3.x IO Scheduler"));
The RxIdler method will wrap that Scheduler with a Espresso IdlingResource and side-effect by registering it to the Espresso class when that Scheduler is first accessed through Schedulers.The initialization can also happen in the onStart() function:
public final class MyTestRunner extends AndroidJUnitRunner {
  @Override public void onStart() {
    RxJavaPlugins.setInitComputationSchedulerHandler(Rx3Idler.create("RxJava 3.x Computation Scheduler"));
    // ...
    super.onStart();
  }
}

Idling resources in Jetpack compose

Idling resources in Jetpack compose work basically the same way as mentioned above. In general it’s possible to use Espresso functions together with compose test functions inside the same test.

The problem with Espresso idling resources

We don’t want to complain about the way Espresso is designed, it’s a great tool to have in a toolchain. But we felt that the IdlingResource is like the Achilles heel of Espresso and it’s worth writing about why.

The main problems of Espresso idling resources are:

  1. Complexity of your test setup is rather high. Look at the documentation, it’s quite a page. You need another gradle dependency which can (and will!) eventually outdate and break. Using RX? Great there is another dependency you can add. Just figure out the right one, cause there are different versions for RX1 and RX2.
  2. For each test you need to keep in mind which IdlingResources will be needed and register them. All of the IdlingResources need to be unregistered after you ran your test (tearDown).
  3. The isIdle method of the IdlingResource is called only every 5 seconds. This makes tests slower than they would need to be.
  4. It’s recommended to package your IdlingResources with your production code. You can add ProGuard rules to not ship your test classes with your APK, but of cause this will again take you some time.
  5. Often you need to modify your classes to reveal internal state of your app. While this is not a disaster, it certainly makes things more complicated when working with testing teams, exclusively responsible for writing tests. They actually should not touch your implementation and meetings might be necessary to discuss the approach.
A result of this introduced complexity is that there are devs who think it’s just not worth it and it would be wiser to spend time writing more integration and unit tests. 

registerIdlingResources method deprecated, and what replaces it?

You might find old code that needs to be upgraded. The API for registering idling resources used to be different:
Espresso.registerIdlingResources(mIdlingResource);
The correct way to register an idling resource nowadays is this one:
IdlingRegistry.getInstance().register(mIdlingResource);

Like this article? there’s more where that came from.

Leave a Reply