A simple, zero-dependency tool for testing multi-threaded code.
ConcurrentUnit allows you to write tests capable of performing assertions or waiting for expected operations across multiple threads, with failures being properly reported back to the main test thread. If an assertion fails, your test fails, regardless of which thread the assertion came from.
Add ConcurrentUnit as a Maven dependency:
<dependency>
<groupId>net.jodah</groupId>
<artifactId>concurrentunit</artifactId>
<version>0.3.3</version>
</dependency>
- Create a
Waiter
- Use
waiter.await
to block the main test thread while waiting for other threads to perform assertions. - Use the
waiter.assert
calls from any thread to perform assertions. - Once expected assertions are completed, use
waiter.resume
call to unblock the main thread.
Optional:
- Use
waiter.expectResumes
to indicate the number ofresume
calls the waiter should expect. This is useful whenresume
may be called by some thread prior toawait
.
Assertion failures will result in the main thread being interrupted and the failure thrown. If a blocking operation times out before all expected waiter.resume
calls occur, the test is failed with a TimeoutException.
Perform an assertion from a worker thread while blocking the main thread until resume
is called:
@Test
public void shouldWaitForResume() throws Throwable {
final Waiter waiter = new Waiter();
// Start worker thread that performs an assertion after some delay, then resumes the waiter
new Thread(new Runnable() {
public void run() {
doSomeWork();
waiter.assertTrue(true);
waiter.resume();
}
}).start();
// Waits for resume to be called
waiter.await(1000);
}
Multiple threads can be used along with any number of expected resume
calls:
@Test
public void shouldWaitForResumes() throws Throwable {
final Waiter waiter = new Waiter();
int expectedResumes = 5;
waiter.expectResumes(expectedResumes);
for (int i = 0; i < expectedResumes; i++) {
new Thread(new Runnable() {
public void run() {
waiter.assertTrue(true);
waiter.resume();
}
}).start();
}
waiter.await(1000);
}
Failed assertions from a worker thread are thrown by the main test thread as expected:
@Test(expected = AssertionError.class)
public void shouldFail() throws Throwable {
final Waiter waiter = new Waiter();
new Thread(new Runnable() {
public void run() {
delayFor(100);
waiter.assertTrue(false);
}
}).start();
waiter.await();
}
TimeoutException is thrown if resume
is not called before the await time is exceeded:
@Test(expected = TimeoutException.class)
public void shouldTimeout() throws Throwable {
new Waiter().await(1);
}
As a more concise alternative to using the Waiter
class, you can extend the ConcurrentTestCase
:
class SomeTest extends ConcurrentTestCase {
@Test
public void shouldSucceed() throws Throwable {
new Thread(new Runnable() {
public void run() {
delayFor(100);
threadAssertTrue(true);
resume();
}
}).start();
await(1000);
}
}
More examples can be found in the WaiterTest or in the following projects:
JavaDocs are available here.
Copyright 2010-2014 Jonathan Halterman - Released under the Apache 2.0 license.