Using awaitility for asynchronous operations in Java integration tests

Often in integration tests, we would like to check the outcome of asynchronous operations. For example, a test to check the automation read a spreadsheet attachement in an email and update the order status correctly. In legacy systems, there are two common reasons for integration with external systems to fail. A library security upgrade could bring in a newer version of the library at compile time, leading to runtime exceptions. (The infamous NoClassFoundException in Java). A system upgrade of the external system can also change the interface and break the integration.

To test the execution of aysnchronous operations, we need the ability to poll at regular intervals, and time out after waiting for long enough. This can be easily achieved with the awaitility library.

To include the library in your tests, add the following in pom.xml

<dependency>
  <groupId>org.awaitility</groupId>
  <artifactId>awaitility</artifactId>
  <version>4.0.3</version>
  <scope>test</scope>
</dependency>

You can then poll for the expected test result like this

with().pollInterval(RESULT_POLL_INTERVAL, TimeUnit.MINUTES)
  .and().with().pollDelay(RESULT_POLL_DELAY, TimeUnit.MINUTES)
  .atMost(RESULT_WAIT, TimeUnit.MINUTES)
  .until(new YourCallable(orderId, jdbcTemplate));

The code starts polling after RESULT_POLL_DELAY minutes, at an interval of RESULT_POLL_INTERVAL minutes. It will try for at most RESULT_WAIT minutes before declaring a failure. The function YourCallable(a, b) is used to determine if the test condition is met.

public class YourCallable implements Callable<Boolean> {	
  private final int orderId;
  private final JdbcTemplate jdbcTemplate;
	
  public YourCallable(final int orderId, final JdbcTemplate jdbcTemplate) {
    this.orderId = orderId;
    this.jdbcTemplate = jdbcTemplate;
  }

  @Override
  public Boolean call() throws Exception {
    boolean accepted = false;
    try {
      Integer acceptanceId = 
        jdbcTemplate().queryForObject("select o.acceptance_id from order o where o.id = ?", Integer.class, orderId);
      if (acceptanceId != null && acceptanceId > 0) {
        accepted = true;
      }
    }
    catch (IncorrectResultSizeDataAccessException e) {
      // SQL null means no acceptance id
      accepted = false;
    }
    return accepted;
  }	
}

By using awaitility, asynchronous testing code becomes a lot more readable than a DIY approach.