Awaitility

Language: Java

Testing/Async

Testing asynchronous operations in Java can be challenging due to timing and concurrency issues. Awaitility was created to simplify this by providing a clean, readable API to wait for conditions without using Thread.sleep() or complex polling logic. It is widely used in unit tests, integration tests, and reactive systems testing.

Awaitility is a Java DSL (Domain Specific Language) for testing asynchronous code. It allows developers to wait for a certain condition to be met within a specified time period, making testing of async operations, background tasks, or event-driven systems more reliable and readable.

Installation

maven: Add dependency in pom.xml: <dependency> <groupId>org.awaitility</groupId> <artifactId>awaitility</artifactId> <version>4.2.0</version> <scope>test</scope> </dependency>
gradle: Add dependency in build.gradle: testImplementation 'org.awaitility:awaitility:4.2.0'

Usage

Awaitility allows you to define expectations using conditions, durations, and polling intervals. It integrates with JUnit, TestNG, and other test frameworks, making async testing straightforward.

Waiting for a flag to be true

import static org.awaitility.Awaitility.await;
import java.util.concurrent.TimeUnit;

boolean[] flag = {false};
new Thread(() -> {
    Thread.sleep(1000);
    flag[0] = true;
}).start();

await().atMost(5, TimeUnit.SECONDS).until(() -> flag[0]);

Waits up to 5 seconds until the `flag` variable becomes true, allowing asynchronous threads to complete.

Waiting for a value to change

import static org.awaitility.Awaitility.await;
import java.util.concurrent.atomic.AtomicInteger;

AtomicInteger counter = new AtomicInteger(0);
new Thread(() -> counter.incrementAndGet()).start();
await().untilAtomic(counter, val -> val > 0);

Waits for an AtomicInteger to become greater than 0.

Custom polling interval

await().atMost(10, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS)
       .until(() -> myService.isReady());

Polls every 500 milliseconds for a condition to become true, with a maximum timeout of 10 seconds.

Ignoring exceptions

await().atMost(5, TimeUnit.SECONDS).ignoreExceptions()
       .until(() -> externalService.getData() != null);

Ignores transient exceptions while waiting for a condition, useful for unstable or delayed async services.

Combining with matchers

import static org.hamcrest.Matchers.*;
await().atMost(3, TimeUnit.SECONDS).until(() -> list.size(), equalTo(5));

Uses Hamcrest matchers to define more expressive conditions.

Error Handling

ConditionTimeoutException: Occurs when the expected condition is not met within the specified timeout. Verify the async logic or increase the timeout duration if necessary.
IllegalStateException: Occurs if Awaitility is misconfigured, e.g., negative timeouts or invalid poll intervals. Ensure proper API usage.

Best Practices

Avoid using Thread.sleep() in tests; prefer Awaitility for readability and reliability.

Define reasonable timeouts to prevent tests from hanging indefinitely.

Use atomic variables or thread-safe constructs for shared state in async tests.

Integrate with JUnit or TestNG to run async tests seamlessly.

Use polling intervals to balance responsiveness and CPU usage during waits.