Testing Asynchronous Systems
Learning how async systems work and how to get them tested, I came across a Java Library (framework) that makes it easier to write tests that are readable, Awaitility.
Awaitility helps to write tests not only more readable format but also makes it easy to test async code. Let's take an example of a system where you are using Rest APIs need to get the acknowledgment from the other system and you have to wait for a certain time to get it tested, in scenarios like these you need to wait or add Thread.sleep() in your test to get the desired acknowledgment before moving forward otherwise you get the assertion error (java.lang.AssertionError ),
Response response = given().
contentType(ContentType.JSON).
header("X-Auth-Token",auth_token).
when().
get(“https://sample URL”);
Thread.sleep(5000);
Assert.assertEquals(response.path(“Status”), “Success”);
Now consider your application has multiple APIs that have the same problem, you'll keep increasing your build time by adding multiple waits. Awaitility helps you cater to these problems by giving,
asyncService.initialize();
await()
.atLeast(Duration.ONE_HUNDRED_MILLISECONDS)
.atMost(Duration.FIVE_SECONDS)
.with()
.pollInterval(Duration.ONE_HUNDRED_MILLISECONDS)
.until(asyncService::isInitialized);
So it will hit for at least 100 milliseconds, at max for 2 seconds, it fails to try again after 100 milliseconds until returns true, we use the conditions in a different manner like Wait forever until the call,
await().until( userRepositorySize(), equalTo(1) );
OR
await().until( fieldIn(object).ofType(int.class), equalTo(2) );
You can refer to the usage of the conditions from their git documentation usage. We can further enhance its performance by adding poll interval, 100 milliseconds with a delay of 20 milliseconds until the condition is true,
with().pollDelay(100, MILLISECONDS).and().pollInterval(200, MILLISECONDS).await().until(<condition>);
By default, Awaitility will wait for 10 seconds and if the size of the user repository is not equal to 1 during this time it'll throw a ConditionTimeoutException failing the test. If you want to set different default timeouts you can define it as,
Recommended by LinkedIn
Awaitility.setDefaultTimeout(..
Awaitility.setDefaultPollInterval(..)
Awaitility.setDefaultPollDelay(..))
Another common problem with asynchronous systems is that it's hard to write readable tests for them that are focused on business logic and are not polluted with synchronizations, timeouts, and concurrency control. With Awaitility, we can express our expectations from the system in an easy-to-read DSL.
asyncService.initialize();
await()
.until(asyncService::isInitialized);
Here, we use await — one of the static methods of the Awaitility class. It returns an instance of a ConditionFactory class. We can also use other methods like given for the sake of increasing readability.
We can also use proxy to provide real method calls for conditions without the implementation of a Callable or lambda expression. Let's use the AwaitilityClassProxy.to static method to check that AsyncService is initialized:
asyncService.initialize();
await()
.untilCall(to(asyncService).isInitialized(), equalTo(true));
The Implementation
We need to add Awaitility dependencies to our pom.xml.
The awaitility library will be sufficient for most use cases. In case we want to use proxy-based conditions, we also need to provide the awaitility-proxy library
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>4.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility-proxy</artifactId>
<version>3.1.6</version>
<scope>test</scope>
</dependency>
In learning TDD with async this Awaitility must be a good help for asynchronous API tests and their implementation. Awaitility library is flexible and easy to use in real projects for testing asynchronous systems. It Allows polling until a certain condition becomes true thereby dealing with processing time.
Learn More:
Excellent 👍
Osama Aqeel