To test asynchronous code, we use the XCTestExpectation class and wait for the expected outcome. The workflow is to create an expectation, and then when the asynchronous task completes successfully, we fulfil that expectation. We will wait for a specific amount of time for the expectation to be fulfilled.
Basically, JUnit doesn't support ordering and the tests should be able to be run in any order. If you really need to have dependencies between tests, use TestNG, where you can have dependencies.
If an operation is asynchronous just because it relies on setTimeout or other time-based behavior, a good way to test it is to use Jasmine's mock clock to make it run synchronously. This type of test can be easier to write and will run faster than an asynchronous test that actually waits for time to pass.
An alternative is to use the CountDownLatch class.
public class DatabaseTest {
/**
* Data limit
*/
private static final int DATA_LIMIT = 5;
/**
* Countdown latch
*/
private CountDownLatch lock = new CountDownLatch(1);
/**
* Received data
*/
private List<Data> receiveddata;
@Test
public void testDataRetrieval() throws Exception {
Database db = new MockDatabaseImpl();
db.getData(DATA_LIMIT, new DataCallback() {
@Override
public void onSuccess(List<Data> data) {
receiveddata = data;
lock.countDown();
}
});
lock.await(2000, TimeUnit.MILLISECONDS);
assertNotNull(receiveddata);
assertEquals(DATA_LIMIT, receiveddata.size());
}
}
NOTE you can't just used syncronized with a regular object as a lock, as fast callbacks can release the lock before the lock's wait method is called. See this blog post by Joe Walnes.
EDIT Removed syncronized blocks around CountDownLatch thanks to comments from @jtahlborn and @Ring
You can try using the Awaitility library. It makes it easy to test the systems you're talking about.
If you use a CompletableFuture (introduced in Java 8) or a SettableFuture (from Google Guava), you can make your test finish as soon as it's done, rather than waiting a pre-set amount of time. Your test would look something like this:
CompletableFuture<String> future = new CompletableFuture<>();
executorService.submit(new Runnable() {
@Override
public void run() {
future.complete("Hello World!");
}
});
assertEquals("Hello World!", future.get());
IMHO it's bad practice to have unit tests create or wait on threads, etc. You'd like these tests to run in split seconds. That's why I'd like to propose a 2-step approach to testing async processes.
Start the process off and wait for the result using a Future
.
One method I've found pretty useful for testing asynchronous methods is injecting an Executor
instance in the object-to-test's constructor. In production, the executor instance is configured to run asynchronously while in test it can be mocked to run synchronously.
So suppose I'm trying to test the asynchronous method Foo#doAsync(Callback c)
,
class Foo {
private final Executor executor;
public Foo(Executor executor) {
this.executor = executor;
}
public void doAsync(Callback c) {
executor.execute(new Runnable() {
@Override public void run() {
// Do stuff here
c.onComplete(data);
}
});
}
}
In production, I would construct Foo
with an Executors.newSingleThreadExecutor()
Executor instance while in test I would probably construct it with a synchronous executor that does the following --
class SynchronousExecutor implements Executor {
@Override public void execute(Runnable r) {
r.run();
}
}
Now my JUnit test of the asynchronous method is pretty clean --
@Test public void testDoAsync() {
Executor executor = new SynchronousExecutor();
Foo objectToTest = new Foo(executor);
Callback callback = mock(Callback.class);
objectToTest.doAsync(callback);
// Verify that Callback#onComplete was called using Mockito.
verify(callback).onComplete(any(Data.class));
// Assert that we got back the data that we expected.
assertEquals(expectedData, callback.getData());
}
There's nothing inherently wrong with testing threaded/async code, particularly if threading is the point of the code you're testing. The general approach to testing this stuff is to:
But that's a lot of boilerplate for one test. A better/simpler approach is to just use ConcurrentUnit:
final Waiter waiter = new Waiter();
new Thread(() -> {
doSomeWork();
waiter.assertTrue(true);
waiter.resume();
}).start();
// Wait for resume() to be called
waiter.await(1000);
The benefit of this over the CountdownLatch
approach is that it's less verbose since assertion failures that occur in any thread are properly reported to the main thread, meaning the test fails when it should. A writeup that compares the CountdownLatch
approach to ConcurrentUnit is here.
I also wrote a blog post on the topic for those who want to learn a bit more detail.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With