Sunday, 10 February 2013

Awaitility - testing asynchronous calls in Java

Asynchronicity plays an important role in systems we develop, nowadays. Below are examples, where we are using asynchronous operations, almost on a daily basis, in our systems:
  • writing to cache with asynchronous write to database behind the scenes or otherwise
  • JMS queues or topics, which are asynchronous itself
  • retrieving data from external systems in asynchronous way
Asynchronous operations are often used for caching data in order to decrease the number of expensive calls to: database, webservice etc. There are also mutations of above caching approach. For instance, one may combine sending time consuming or complicated algorithmic operations to JMS queue (asynchronous system). Later on, when results of such an intensive operation/algorithm would be available, it might be simply cached in a local in-memory storage. Next time, when user would try to fetch same data, it will be fast and easy for her to retrieve previously calculated results from cache. Such an approach, gives you an ability to build very fast, scaleable, flexible and user friendly applications/GUIs.
However, there is one caveat here. It is quite difficult to test such an asynchronous system. If we like to do it from scratch, we will need to provide some threads' handlers, timeouts and generally deal with concurrency, which makes the whole test more obscure and less focused on business principles ruling the entire system. If above situation is true, it means that we found a niche and basically a need for some library to handle our problem. 
The answer for our headache is Awaitility. It's a quite simple and powerful, Java based library, to test asynchronous calls. Moreover, it does have a concise and expressive DSL to define expectations.




Now, let's see Awaitility in action. 
I wrote a simple application, which is available on GitHub. The basic idea is that there is a DelayedFileCreator class, which is responsible for creating a file on a filesystem. It also tries to mimic an expensive and time consuming calculations, by time delay. Important thing is a result of that operation. This sort of asynchronous calls are having either a state returned or they cause a change of state somewhere else - in our case it is a newly created file on a filesystem. 


package asynchronousexample;

import java.io.File;
import java.io.IOException;

public class DelayedFileCreator implements Runnable {

 private static final int THREE_SECONDS = 3000;

 private File file;
 private Timer timer;

 public DelayedFileCreator(Timer aTimer, File aFile) {
  timer = aTimer;
  file = aFile;
 }

 @Override
 public void run() {
  sleepBeforeCreatingFile();
  createNewFile();
 }

 private void sleepBeforeCreatingFile() {
  try {
   timer.sleep(THREE_SECONDS);
  } catch (InterruptedException ie) {
   throw new RuntimeException(ie);
  }
 }

 private void createNewFile() {
  try {
   file.createNewFile();
  } catch (IOException ioe) {
   throw new RuntimeException(ioe);
  }
 }
}
 
Ideally, we would like to be able to write a test, which invokes overridden method run(), waits until it is done and asserts the result of asynchronous operation. Awaitility hits the nail on the head. Below example shows an integration test, using nice and readable DSL, to assert the result.


package integration;

import asynchronousexample.AsynchronousTaskLauncher;
import asynchronousexample.DelayedFileCreator;
import asynchronousexample.Timer;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import java.io.File;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static com.jayway.awaitility.Awaitility.with;
import static com.jayway.awaitility.Duration.ONE_HUNDRED_MILLISECONDS;
import static com.jayway.awaitility.Duration.TEN_SECONDS;
import static com.jayway.awaitility.Duration.TWO_HUNDRED_MILLISECONDS;
import static org.hamcrest.Matchers.equalTo;

public class CreateFileAsynchronouslyIntegrationTest {

 private static final int THREAD_POOL_SIZE = 3;
 private static final String FILENAME = "sample.txt";

 @BeforeTest
 public void deleteFileFromFileSystem() {
  File file = new File(FILENAME);
  if (file.exists()) {
   file.delete();
  }
 }

 @Test
 public void shouldAsynchronouslyWriteFileOnDisk() throws Exception {
  AsynchronousTaskLauncher launcher = prepareAsynchronousTaskLauncher();
  Runnable delayedFileCreatorTask = prepareDelayedFileCreatorWith(FILENAME);

  launcher.launch(delayedFileCreatorTask);

  with().pollDelay(ONE_HUNDRED_MILLISECONDS)
    .and().with().pollInterval(TWO_HUNDRED_MILLISECONDS)
    .and().with().timeout(TEN_SECONDS)
    .await("file creation")
    .until(fileIsCreatedOnDisk(FILENAME), equalTo(true));
 }

 private Runnable prepareDelayedFileCreatorWith(String filename) {
  Timer timer = new Timer();
  File file = new File(filename);
  return new DelayedFileCreator(timer, file);
 }

 private AsynchronousTaskLauncher prepareAsynchronousTaskLauncher() {
  ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
  return new AsynchronousTaskLauncher(executorService);
 }

 private Callable fileIsCreatedOnDisk(final String filename) {
  return new Callable() {
   public Boolean call() throws Exception {
    File file = new File(filename);
    return file.exists();
   }
  };
 }
}

 
The whole beauty lies in readability and expressiveness of Awaitility. We do not have to take care about threads' handling, concurrency aspects etc. Everything is being done by Awaitility. 

I really encourage you to use this small, but very handy library to test your asynchronous calls.