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.

Junit 5 parameterized tests

My favourite feature of Junit 5 is its improvement to parameterized tests. Previously, each parameterized test must be written in its own class. In the test class, you define a method for the test and another method for its inputs and outputs. Junit 5 provides a much simpler way to define parameterized tests. You can now annotate individual test methods as parameterized, with parameters supplied via annotations.

For example, the following test verifies the method under test throws an InvalidRequestException with the specified list of ISO dates.

@ParameterizedTest
@ValueSource(strings= {"1752-12-31T21:45:00", "10000-01-01T21:45:00"})
public void invalidDate(String testdate) {
    testRequest.setDate(testdate);
    assertThrows(InvalidRequestException.class, 
        () -> testService.testMethod(ORDER_ID, testRequest));
}

With the enum value source and exclude mode, it’s now very easy to test status validation to make sure all statuses except a few are allowed. For example, the code below checks an order can be cancelled only in the PREPROCESS status.

@ParameterizedTest
@EnumSource(value = OrderStatus.class, mode = Mode.EXCLUDE, 
            names = {"PREPROCESS"})
public void testOrderStatusCancellation(OrderStatus status) throws Exception {
    testOrder.setStatus(status);
    assertThrows(InvalidRequestException.class
        () -> testService.cancel(testOrder));
}

If your parameterized test inputs and outputs are more complicated, and cannot be easily supplied inside annotation, you can use the @MethodSource annotation. This allows you to define your test method inputs and outputs with another method, similar to how things were before Junit 5.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

@ParameterizedTest
@MethodSource("inputOutputProvider")
public void testCodeParsing(String input, Set<Integer> expected) {
    assertEquals(expected, codeReader.getRejectionCodes(comment));
}

private static Stream<Arguments> inputOutputProvider() {
    return Stream.of(
        Arguments.arguments("0010,0015,0041", newHashSet(10,15,41)),
        Arguments.arguments("C22", newHashSet(22)),
        Arguments.arguments("10", newHashSet(10)));
}

More hamcrest collections goodness

I have been using Hamcrest more in my unit tests. JUnit 4.11 included only a portion of the matchers available in Hamcrest 1.3. (The ones packaged in hamcrest-core specifically). To include other useful matchers from Hamcrest, add the following to the maven pom.xml

		<dependency>
			<groupId>org.hamcrest</groupId>
			<artifactId>hamcrest-library</artifactId>
			<version>1.3</version>
			<scope>test</scope>
		</dependency>

I found the collections one very handy. For example, to test the size of a list:

import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.Assert.assertThat;
...
List list = new ArrayList();
assertThat(list, hasSize(0));

JUnit 4.11 and its new Matchers

I have never used the Hamcrest matchers with JUnit before. Not until last week. I noticed in the release note that JUnit 4.11 included Hamcrest 1.3, with its Matchers and improved assertThat syntax. Reading the examples on the release note, I was intrigued.

To use the new Matchers and assertThat, you need to include the following imports

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
Number Objects

The first improvement I noticed were comparison with Java number objects.

Long l = new Long(10);
assertEquals(10L, l);
assertThat(l, is(10L));

With the old assertEquals, the compiler would complain about The method assertEquals(Object, Object) is ambiguous for type X. You need to change both parameters to either long values or Long objects for the assert to work, for example

assertEquals(10L, l.longValue());

On the other hand, assertThat and the is() matcher works just fine.

Collections

I saw a few very handy looking matchers for Collections from looking at the CoreMatchers javadoc. For example, hasItem, hasItems, everyItem. I had the opportunity to use hasItems in my unit tests last week to check if a List object contains items from a given list of values. It was as simple to use as this

assertThat(list, hasItems("apples", "oranges"));

I’m a fan of this new way of matching things in JUnit.