Awaitility with Spring Boot

Sometimes we have to write asynchronous RESTfull APIs to have asynchronous relation with the caller. Most of the create, update or delete APIs should be written in asynchronous mode. Since, the caller is expecting the response in form of acknowledgement only.

Something like, when you place your order in Amazon, what we get an acknowledgement from the vendor that your order request have been taken and you will be informed in sometime whether the order can be taken further or now. That’s it.

So this blog is not for really, how to write an asynchronous RESTfull APIs. Its more on the, HOW TO TEST THOSE ASYNCHRONOUS APIs. And in this blog we are going to talk about Awaitility which is one of the best asynchronous testing framework to test such APIs.

I am taking help of an Spring Boot application and the same can be found at GITHUB. Now first we see whats the problem with existing JUnit framework.

Lets Code: –

Lets start with one rest controller. For the simplicity purpose my POST API is not taking anything in the request body. Its always adding the same Student with different StudentID.

@RestController
public class MyController {

    private StudentService studentService;

    @Autowired
    public MyController(StudentService studentService) {
        this.studentService = studentService;
    }

    @GetMapping("/student")
    public List<Student> getStudent()  {
        return studentService.getStudent();
    }

    @PostMapping("/student")
    public String addStudent()  {
        return studentService.addStudent();
    }
}

And a Service

@Service
public class StudentService {

    private final int INIT_DELAY = 2000;
    private Executor executor = Executors.newFixedThreadPool(4);
    private volatile Student student = null;
    private volatile boolean isActive = false;

    private List<Student> students = new ArrayList<>();
    public List<Student> getStudent(){
        return students;
    }

    public Boolean isStudentActive() {
        return isActive;
    }

    public String addStudent() {
        executor.execute(() -> {
            try {
                sleep(INIT_DELAY);
                isActive = true;
                Address address = new Address("first", "second", "third");
                Name name = new Name("first Name" , "second name");
                student = new Student(address, name, Boolean.TRUE);
                students.add(student);
            } catch (InterruptedException e) {
                //Print the error logs.
            }
        });

        return "Student Added";
    }
}

As you can see in above two classes that we have one POST (AddStudent) and another GET (GetStudent) services and the for the POST service we are waiting for 2 second. To prepare this blog only, I mentioned that 2 second deliberately. The same time can be spent anywhere else too. Like any other IO calls.

Problem Starts:

If you write one integration test for this application, you would write something below class.

@RunWith(SpringRunner.class)
@WebMvcTest(MyController.class)
public class SpringBootDemoApplicationTests {

    @Autowired
    private MockMvc mvc;

    @Autowired
    private StudentService studentService;

    @Test
    public void addStudentTest() throws Exception {

        mvc.perform(MockMvcRequestBuilders
                .post("/student")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());

        //Thread.sleep(2000); //Uncomment this line to make this test case run
        Assert.assertTrue(studentService.isStudentActive());
    }
}

If you run above test case, it will fail saying java.lang.AssertionError at line # 20. Because the student is still not active. You will have to uncomment the code at line # 19 to make this test case run.

Now say, if you have few more such POST APIs and you have to write more test cases(positive and all negative scenarios) and in every test case you are writing this Thread.sleep(…). Then only god can save your build timings.

To get rid of all these static mentioning of Thread.sleep(…) lets use the framework Awaitility. Its a small Java DSL for synchronizing (waiting for) asynchronous operations. It makes it easy to test asynchronous code.

But what to do?

To add Awaitility in your application all you have to do is first, add dependency in pom.xml

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

update your test case something like below.


@RunWith(SpringRunner.class)
@WebMvcTest(MyController.class)
public class SpringBootDemoApplicationTests {

    @Autowired
    private MockMvc mvc;

    @Autowired
    private StudentService studentService;

    @Test
    public void addStudentTest() throws Exception {

        mvc.perform(MockMvcRequestBuilders
                .post("/student")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());

        await()
            .atLeast(Duration.ONE_HUNDRED_MILLISECONDS)
            .atMost(Duration.TWO_SECONDS)
            .with()
            .pollInterval(Duration.ONE_HUNDRED_MILLISECONDS)
            .until(studentService::isStudentActive);
    }
}

What the above new lines are saying is, hit for at least 100 MS, at max for 2 seconds, if fails try again after 100 MS until studentService.isStudentActive returns true.

Examples: –

  • Wait at most 5 seconds until customer status has been updated:
await().atMost(5, SECONDS).until(customerStatusHasUpdated());
  • Wait forever until the call to orderService.orderCount() is greater than 3.
await().forever()
    .untilCall(to(orderService).orderCount(), greaterThan(3));
  • Wait 300 milliseconds until field in object myObject with name fieldName and of type int.class is equal to 4.
await().atMost(300, MILLISECONDS)
    .until(fieldIn(orderService).withName("fieldName")
    .andOfType(int.class), equalTo(3));
  • Advanced usage: Use a poll interval of 100 milliseconds with an initial delay of 20 milliseconds until customer status is equal to “REGISTERED”. This example also uses a named await by specifying an alias (“customer registration”). This makes it easy to find out which await statement that failed if you have multiple awaits in the same test.
with().pollInterval(ONE_HUNDERED_MILLISECONDS)
    .and()
    .with()
    .pollDelay(20, MILLISECONDS)
    .await("customer registration")
    .until(customerStatus(), equalTo(REGISTERED));
  • You can also specify a default timeout, poll interval and poll delay using:
 Awaitility.setDefaultTimeout(..)
 Awaitility.setDefaultPollInterval(..)
 Awaitility.setDefaultPollDelay(..)

Hope this must have cleared about your doubt on, how to test asynchronous rest APIs.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.