Improved Spring Integration test support with Mockito and Hamcrest

Iwein Fuld

Last week I've added a new test bundle to Spring Integration. This bundle contains matchers to help writing tests for Spring Integration configurations. It builds on JUnit 4, Hamcrest and Mockito. Even if you're not a user of Spring Integration, I will make sure that this post gives you a few ideas for using matchers to write more readable tests.

First a few pointers to Spring Integration to give you some background.

Spring Integration is a framework that aims to implement Enterprise Integration Patterns as described in the book by Hohpe and Woolf. The book is nice, but using those patterns will require some boilerplate and plumbing that is a good match for a framework, hence Spring Integration. JUnit 4 you should know about already, but a feature that was added recently deserves another mention. In the latest versions of JUnit you can use the assertThat(T actual, Matcher<T> matcher), which builds on Hamcrest matchers. This already makes your assertions much more readable, but combined with Mockito, test cases become poetry. I will assume for now that I don't need to tell you about all this, but if you have no clue what I'm talking about you should really enlighten yourself.

EIP frequently involves asynchronous handoff, also it involves wrapping object payloads in messages. Both of these can be in the way when writing tests. Luckily we can easily abstract this boilerplate away. Today I've committed the solution for the unwrapping problem, I'll talk about a solution to the second problem another time.

Unwrapping messages requires some boilerplate that makes the test harder to read. The following snippet from the Spring Integration's AggregatorEndpointTests clearly shows both problems.

@Test
public void testCompleteGroupWithinTimeoutWithSameId() throws Exception {
//...
Message<?> reply = replyChannel.receive(500);
assertNotNull(reply);
assertEquals("123456789", reply.getPayload());
}

In a few seconds it's clear what we're trying to do here, but it doesn't look very elegant to me. With the new test project we aim to make this type of code more readable. To do this we've created some utility classes.

First there are the HeaderMatcher and PayloadMatcher classes. They allow you to do things like:

import static ...HeaderMatcher.*;
@Test
public void testCompleteGroupWithinTimeoutWithSameId() throws Exception {
//...
Message<?> reply = replyChannel.receive(500);
assertThat(reply, hasPayload(is(String.class))); //passing in a Matcher
assertThat(reply, hasPayload("123456789")); //based on equals check
}

The HeaderMatcher produces matchers that inspect the headers of a message in a similar way.

This is topped off with a little Mockito magic with the MockitoMessageMatchers:

@Test
public void anyMatcher_withWhenArgumentMatcherAndEqualPayload_matching() throws Exception {
when(channel.send(messageWithPayload(SOME_PAYLOAD))).thenReturn(true);
assertThat(channel.send(message), is(true));
}

I'm very happy with the elegance of this and I think these techniques are very useful in any domain. Taking a bit of extra time to create matcher utilities that actually match with your domain, will pay you back in test case maintenance. Whenever you find yourself using argThat(..) in Mockito a lot, or scattering about BaseMatcher implementations, think about this blog.

I hope you've found this example useful, if you want to look into the details of the Spring Integration test project you can look at the commits around this issue. If you something wacky be sure to tell me or someone else in the Spring Integration team before the next release.

Last but not least: Kudos to Alexander Peters, who wrote a patch for this.

Comments (0)

    Add a Comment