Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Toolkit for multi-threaded testing

README.md

ConcurrentUnit Build Status

A simple, zero-dependency tool for performing assertions across threads while unit testing.

Introduction

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.

Setup

Add ConcurrentUnit as a Maven dependency:

<dependency>
  <groupId>net.jodah</groupId>
  <artifactId>concurrentunit</artifactId>
  <version>0.3.2</version>
</dependency>

Usage

  • 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 of resume calls the waiter should expect. This is useful when resume may be called by some thread prior to await.

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.

Examples

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);
}

Alternatively

As a more consise alternative to using the Waiter class directly, you can extend the ConcurrentTestCase convenience class:

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);
    }
}

Other Examples

More examples can be found in the WaiterTest or in the following projects:

Docs

JavaDocs are available here.

License

Copyright 2010-2014 Jonathan Halterman - Released under the Apache 2.0 license.

Something went wrong with that request. Please try again.