Concurrent testing is hard, but not as hard as you think. If you use the right tricks it can be done. This blog shows you one particular trick that uses a latch and a mock to ensure a test scenario is completed before running the verifications.

While working on Spring Integration in Action, I experimented with a neat solution for concurrent tests. When I showed it to some colleagues I was pleasantly surprised by the reaction that I got. Judge for yourself if it's worth the blog.

The main idea is to use a latch and let your mock count it down. It sounds trivial (and to be honest it is).

First let me show you how it would be done without mocking:

@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class ConcurrentTest {

  @Autowired @Qualifier("in") MessageChannel in;

  @Autowired @Qualifier("out") PollableChannel out;

  @Autowired Service service;

  @Test(timeout = 5000)
  public void shouldGoThroughPipeline() throws Exception {
    in.send(MessageBuilder.withPayload("test").build());

    service.invoked.await();
    out.receive();
  }

  public static class Service {
    private CountDownLatch invoked = new CountDownLatch(1);

    public String serve(String input) {
      invoked.countDown();
      return input;
    }
  }
}

A message is sent on channel "in", which will result in an asynchronous invocation of the service. I'm using Spring Integration to make the test asynchronous, but I could just as easily have used @Async, Camel, JMS or what not. The basic idea remains the same.

This is already mostly painless, but we clean up the test a bit more by using a mock. You can wire a mock into your context using a factory-method:

<bean id="service"  class="org.mockito.Mockito" factory-method="mock">
  <constructor-arg value="iwein.samples.test.concurrent.ConcurrentTest$Service"/>
</bean>

Now this mocked service will be autowired into the test and you can make it do tricks:

  @Test(timeout = 5000)
  public void shouldGoThroughPipeline() throws Exception {
    final CountDownLatch serviceInvoked = new CountDownLatch(1);
    given(service.serve("test")).willAnswer(new Answer(){
      public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
        serviceInvoked.countDown();
        return "test";
      }
    });

    in.send(MessageBuilder.withPayload("test").build());

    serviceInvoked.await();
    out.receive();
  }

The answer passed into willAnswer() method will first count down the latch and then return "test". This works, but it's not the most readable code. Let's tidy it up a bit:

@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class ConcurrentTest {

  @Autowired @Qualifier("in") MessageChannel in;

  @Autowired @Qualifier("out") PollableChannel out;

  @Autowired Service service;

  @Test(timeout = 5000)
  public void shouldGoThroughPipeline() throws Exception {
    //given
    final CountDownLatch serviceInvoked = new CountDownLatch(1);
    given(service.serve("test")).willAnswer(latchedAnswer("test", serviceInvoked));

    //when
    in.send(MessageBuilder.withPayload("test").build());
    serviceInvoked.await();

    //verify
    Message<?> message = out.receive();
    assertThat((String) message.getPayload(), is("test"));
  }

  private <T> Answer<T> latchedAnswer(final T returning, final CountDownLatch latch) {
    return new Answer(){
      public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
        latch.countDown();
        return returning;
      }
    };
  }

  //You'd have a class in main to mock, but an interface here will serve the example
  public static interface Service {
    public String serve(String input);
  }
}

That's really polite code for a concurrent test don't you think?

I'm thinking of adding the latchedAnswer method into a test utility that comes with Spring Integration, but as you can see it fits quite well in your copy buffer too. The point is that even without using a concurrent test framework (I know they're out there) simple concurrent tests can be as simple as they should be.

Caveats:

  • If you pass an instance of the wrong type into latchedAnswer you'll not get a compiler warning, because of a generics issue with Mockito
  • When you replace a service with a mock, frameworks using reflection around it might get a bit confused. For this example I had to add a method attribute to the service activator element that wouldn't have been needed without the mock.
  • Allways set a timeout. You don't want the test to run eternally on your build server if you are unfortunate enough to break it.

Source code for this example can be found on github. Enjoy!