- 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
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 CallablefileIsCreatedOnDisk(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.
No comments:
Post a Comment