Unit Testing Asynchronous Code
I try to avoid using code that instantiates threads from unit-tests. They’re awkward to write, brittle and I would rather extract the controlling thread, using the test as the controller. However there are times when it’s unavoidable.
Here’s an example. A
PriceFinder
is a class that
goes and retrieves a price for a symbol
asyncronously, returning immediately.
Sometime later it receives a response and
performs a callback with the result. I’ve
left out the implementation of how it
actually does this (maybe a web-service or
database call).
public class PriceFinder {
public void findPrice(String symbol, PriceCallback callback) { ... }
}
public interface PriceCallback {
void foundPrice(Price price);
}
To test this, a callback can be used from the test case that records what is passed in. The test can then wait for a specified time and assert that the correct result is received. If the asyncronous code does not complete within the specified time, the test will fail (suggesting the code is either running very slowly or is never going to complete).
public class PriceFinderTest extends TestCase {
private PriceFinder finder = new PriceFinder();
private Price receivedPrice;
public void testRetrievesAValidPrice() throws Exception {
// Start the asynchronous call
.findPrice("MSFT", new PriceCallback() {
finderpublic void foundPrice(Price price) {
// Callback stores the result for test
= price;
receivedPrice }
});
// Wait two seconds (URGGGGGH)
Thread.sleep(2000);
// Assert that the result was received
assertNotNull("Expected a price", receivedPrice);
}
}
However, this sucks as it will slow your
test suite right down if you have loads of
tests using Thread.sleep()
.
It’s also somewhat brittle. Is 2 seconds
enough? If it takes longer the test will
fail. Extending it will make the tests even
slower.
A less time consuming way to do it is by
using wait()
and
notify()
on a lock. The handler
notifies the lock and the test waits for
this notification. In case this notification
never happens, a timeout is used when
calling wait()
.
public class PriceFinderTest extends TestCase {
private PriceFinder finder = new PriceFinder();
private Price receivedPrice;
private Object lock = new Object(); // <--- Coordination lock
public void testRetrievesAValidPrice() throws Exception {
// Start the asynchronous call
.findPrice("MSFT", new PriceCallback() {
finderpublic void foundPrice(Price price) {
= price;
receivedPrice synchronized(lock) {
.notify();
lock}
}
});
// Wait two seconds or until the monitor has been notified.
synchronized(lock) {
.wait(2000);
lock// But there's still a problem...
}
assertNotNull("Expected a price", receivedPrice);
}
}
This optimistic approach results in fast running tests while all is good. If the PriceFinder is behaving well, the test will not wait any longer than the PriceFinder takes to complete its work. If PriceFinder has a bug in it and never calls the handler, the test will fail in at most two seconds.
However, there’s still a subtle issue. In
the case that the PriceFinder
is really fast, it may call
notify()
before the test starts
wait()
ing. The test will still
pass, but it will wait until the timeout
occurs.
This is where threading and synchronization get messy and beyond me.
Doug Lea has a nice little class called
Latch
in his concurrency
library (available in JDK 1.5 as java.util.concurrent.CountDownLatch
).
A latch can only be locked once, once released it will never lock again.
public class PriceFinderTest extends TestCase {
private PriceFinder finder = new PriceFinder();
private Price receivedPrice;
private Latch latch = new Latch();
public void testRetrievesAValidPrice() throws Exception {
.findPrice("MSFT", new PriceHandler() {
finderpublic void foundPrice(Price price) {
= price;
receivedPrice .release();
latch}
});
// Wait until the latch is released or a timeout occurs.
// Importantly: if the latch has already been released,
// proceed immediately.
.attempt(2000);
latch
assertNotNull("Expected a price", receivedPrice);
}
}
That’ll do it. Fast, simple, and robust.