The Awaitility library introduces a functional style usage to express expectations of an asynchronous system in a concise and easy to read manner. Let’s have a deep dive into it with this tutorial.
Waiting in Java properly
In order to make a pause during the execution of Java code you can use two main approaches:
Java’s Thread.sleep() method is a built-in method that allows a thread to pause execution for a specified amount of time. It is simple to use and can be integrated easily into any Java code. However, it is not very flexible as it can only pause execution for a fixed amount of time, and it does not provide any way to check if a certain condition has been met before continuing execution.
Awaitility is a third-party library that provides a more flexible and powerful way to handle thread pauses and waiting for conditions in Java. It provides a variety of methods for waiting for specific conditions, such as waiting for a method to return a certain value or for a specific exception to be thrown. Additionally, Awaitility allows for more fine-grained control over the waiting process, such as setting a timeout for the wait and polling the condition at a specific interval.
Here are the advantages and disadvantages of each solution:
Pros of Thread.sleep():
- Simple to use
- Built-in to Java
Cons of Thread.sleep():
- Only allows for pausing execution for a fixed amount of time
- No way to check if a certain condition has been met before continuing execution
Pros of Awaitility:
- More flexible and powerful way to handle thread pauses and waiting for conditions
- Provides various methods for waiting for specific conditions
- Allows for more fine-grained control over the waiting process
Cons of Awaitility:
- Third-party library, not built-in to Java
Testing applications with Awaitility
Awaitility (as the name says) checks expectations based on a timing condition. Here is a basic example of it:
await().atMost(5, SECONDS).until(checkCondition());
In the above example, checkCondition contains the condition to verify. Therefore, we will wait at most 5 seconds to verify that condition is true. If you don’t specify any timeout Awaitility will wait for 10 seconds and then throw a ConditionTimeoutException if the condition has not been fulfilled.
Even in this simple form, Awaitility has some advantages over Thread.sleep(). Indeed we don’t have to wait for a fixed amount of time if the condition is already verified.
Let’s see a slightly more complex example. In this example, we are introducing untilAsserted which waits until a Runnable supplier execution passes. In this example, we are asserting that in, at most 3 seconds, the server log file will contain a text in it:
await().atMost(3, TimeUnit.SECONDS).untilAsserted(() -> { String content =null; try { Path filePath = Path.of("/home/jboss/wildfly-26.1.0.Final/standalone/log/server.log"); content = Files.readString(filePath); } catch (IOException exc) { exc.printStackTrace(); } Assertions.assertTrue(content.indexOf("Admin console listening") > -1); });
If your tests include several checks from , it is a good idea to introduce an alias for it. This way you will be able to catch easily which test is failing:
await("Check my Heros").atMost(3, SECONDS).until(herosAvailable());
Checking Types
Next, let’s see how to verify an assertion which requires checking a time limit and the returned Type, including its size.
Here is an Endpoint which produces an array of Hero objects:
@GET public Hero[] get() { Hero hero[] = new Hero[3]; hero[0] = new Hero("Spiderman"); hero[1] = new Hero("Batman"); hero[2] = new Hero("Hulk"); return hero; }
The following condition will check that, within 2 seconds, the HTTP GET endpoint returns an array of Hero with a lenght of 3:
await().atMost(2, SECONDS) .untilAsserted(() -> Assertions.assertEquals(3, get().as(Hero[].class).length));
Using Poll intervals
A poll interval tells Awaitility how often it should evaluate the condition that is supplied to the until method. In the following example, we use a poll interval of 250 milliseconds with an initial delay of 50 milliseconds until
with().pollInterval(250, TimeUnit.MILLISECONDS).and().with().pollDelay(50, MILLISECONDS).await().until(herosAvailable());
Check the condition between a time frame
You can also check that the await condition is true only between a specific timing. For this purpose, you can use atLeast in your condition. For example:
await().atLeast(50, TimeUnit.MILLISECONDS).and().atMost(3, SECONDS).until(herosAvailable());
In the above example, it is expected that the condition evaluates to true between 50 ms and 3 seconds. If it evaluates to true before, you will see the following Exception:
Condition was evaluated in 10 milliseconds which is earlier than expected minimum timeout 50 milliseconds
Check condition during an amount of time
Finally, it’s possible to assert that a condition evaluates to true during a time frame. For example, if we were to check that the endpoint returns the same number of Hero during 800 to 1500 ms:
await().during(800, MILLISECONDS).atMost(1500, MILLISECONDS).untilAsserted(() -> Assertions.assertEquals(3, get().as(Hero[].class).length));
In the above example, there won’t be any Exception if the condition evaluates to true earlier than the time frame.
How to build projects using Awaitility
Finally, in order to build Test classes using Awaitility you need to include the following dependency in your pom.xml
<dependency> <groupId>org.awaitility</groupId> <artifactId>awaitility</artifactId> <version>4.2.0</version> <scope>test</scope> </dependency>
Conclusion
This article was a quick walkthough the Awaitility DSL API. You can find the source code for this example on GitHub: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/awaitility