Android Flow Chart

Alternatives to Idling Resources in Compose tests : the waitUntil APIs (updated)

Jose Alcérreca
Android Developers
4 min readApr 22, 2022

--

In this article you’ll learn how to use the waitUntil test API in Compose to wait for certain conditions to be met. This is a good alternative to using Idling Resources in some situations.

[2023 update] Tldr: use the new waitUntil APIs to synchronize in Compose tests (v1.4.0+).

What is synchronization?

One way to categorize tests is by their scope. Small tests, or unit tests, focus on small pieces of your app while big tests, or end-to-end, cover a large portion of your app. You can read about this and other types of tests in the newly updated testing documentation.

Different test scopes in an app

Synchronization is the mechanism that lets the test know when to run the next operation. The bigger the chunk of code you choose to verify, the harder it is to synchronize with the test. In unit tests it’s easy to have full control of the execution of the code to verify. However, as we grow the scope to include more classes, modules and layers, it gets tricky for the test framework to know if the app is in the middle of an operation or not.

Correct synchronization between test and app

androidx.test and, by extension, Compose Test, use some tricks under the hood so that you don’t have to worry too much about this. For example, if the main thread is busy, the test pauses until it can execute the next line.

However, they can’t know everything. For example, if you load data in a background thread, the test framework might execute the next operation too soon, making your test fail. The worst situation is when this happens only a small percentage of the time, making the test flaky.

Option 1: Idling Resources

Idling Resources are an Espresso feature that lets you, the developer, decide when the app is busy. You have two ways to use them:

1. Installing them in the framework or library that is doing work that the test can’t see.

A good example of this is RxIdler, which wraps an RxJava scheduler. This is the preferred way to register Idling Resources because it lets you keep your test setup cleanly split from the test code.

2. Modifying your code under test to explicitly expose information about whether your app is busy or not.

For example, you could modify your repository (or a test double) to indicate that is busy while loading data from a data source:

This is not ideal because you’re polluting your production code, or creating complicated test doubles, and there are some situations when they’re hard to install. For example, how would you use Idling Resources in a Kotlin Flow? Which update is the final one?

Instead, we can wait for things.

Option 2: Waiting for things… the wrong way

Loading data is usually fast, especially when using fake data, so why waste time with idling resources when you can just make the test sleep for a couple of seconds?

This test will either run slower than needed or fail. When you have hundreds or thousands of UI tests, you want tests to be as fast as possible.

Also, sometimes emulators or devices misbehave and jank, making that operation take a bit longer than those 2000ms, breaking your build. When you have hundreds of tests this becomes a huge issue.

Option 3: Waiting for things the right way!

If you don’t want to modify your code under test to expose when it’s busy, another option is to wait until a certain condition is met, instead of waiting for an arbitrary amount of time.

In Compose, you can leverage the waitUntil function, which takes another function that produces a boolean.

2023/03/22 update: from Compose 1.4.0, we added a new set of waitUntil APIs:

[Before 1.4.0: Use these helpers: waitUntilExists, waitUntilNodeCount]

…and use them like this:

Use these APIs only when you need to synchronize your test with the UI. Synchronizing on every test statement pollutes the test code unnecessarily, making it harder to maintain.

When should you use it then? A good use case for it is loading data from an observable (with LiveData, Kotlin Flow or RxJava). When your UI needs to receive multiple updates before you consider it idle, you might want to simplify synchronization using waitUntil.

For example, when you collect a Flow from a view:

And you emit multiple items to it:

If repository takes an indeterminate amount of time to come back with the first result, the test framework will think “Loading” is the idle state (the initial value assigned in collectAsState) and continue with the next statement.

So, you can make the test much more reliable if you make sure the UI is not showing the loading indicator:

Happy… wait for it… testing!

Code snippets license:

Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0

--

--

Jose Alcérreca
Android Developers

Developer Relations Engineer @ Google, working on Android