We're constantly fighting a battle when developing the new MongoDB Java driver between using tools that will do heavy lifting for us and minimising the dependencies a user has to download in order to use our driver. Ideally, we want the number of dependencies to be zero.
This is not going to be the case when it comes to testing, however. At the very least, we're going to use JUnit or TestNG (we used testng in the previous version, we've switched to JUnit for 3.0). Up until recently, we worked hard to eliminate the need for a mocking framework - the driver is not a large application with interacting services, most stuff can be tested either as an integration test or with very simple stubs.
Recently I was working on the serialisation layer - we're making quite big changes to the model for encoding and decoding between BSON and Java, we're hoping this will simplify our lives but also make things a lot easier for the ODMs (Object-Document Mappers) and third party libraries. At this level, it makes a lot of sense to introduce mocks - I want to ensure particular methods are called on the writer, for example, I don't want to check actual byte values, that's not going to be very helpful for documentation (although there is a level where that is a sensible thing to do).
We started using JMock to begin with, it's what I've been using for a while, and it gave us what we wanted - a simple mocking framework (I tried Mockito too, but I'm not so used to the failure messages, so I found it really hard to figure out what was wrong when a test failed).
I knew from my spies at LMAX that there's some Groovy test framework called Spock that is awesome, apparently, but I immediately discarded it - I feel very strongly that tests are documentation, and since the users of the Java driver are largely Java developers, I felt like introducing tests in a different language was an added complexity we didn't need.
Then I went to GeeCON, and my ex-colleague Israel forced me to go to the talk on Spock. And I realised just how wrong I had been. Far from adding complexity, here was a lovely, descriptive way of writing tests. It's flexible, and yet structured enough get you thinking in a way that should create good tests.
Since we're already using gradle, which is Groovy as well, we decided it was worth a spike to see if Spock would give us any benefits.
During the spike I converted a selection of our tests to Spock tests to see what it looks like on a real codebase. I had very specific things I wanted to try out:
- Mocking
- Stubbing
- Data driven testing
In the talk I also saw useful annotation like @Requires
, which I'm pretty sure we're going to use, but I don't think it's made it into a build yet.
So, get this, I'm going to write a blog post with Actual Code in. Yeah, I know, you all thought I was just a poncy evangelist these days and didn't do any real coding any more.
First up, Mocking
So, as I said, I have a number of tests checking that encoding of Java objects works the way we expect. The easiest way to test this is to mock our BSONWriter class to ensure that the right interactions are happening against it. This is a nice way to check that when you give an encoder a particular set of data, it gets serialised in the way BSON expects. These tests ended up looking something like this:
@Test
public void shouldEncodeListOfStrings() {
final List<String> stringList = asList("Uno", "Dos", "Tres");
context.checking(new Expectations() {{
oneOf(bsonWriter).writeStartArray();
oneOf(bsonWriter).writeString("Uno");
oneOf(bsonWriter).writeString("Dos");
oneOf(bsonWriter).writeString("Tres");
oneOf(bsonWriter).writeEndArray();
}});
iterableCodec.encode(bsonWriter, stringList);
}
(Yeah, I'm still learning Spanish).
So that's quite nice, my test checks that given a List of Strings, they get serialised correctly. What's not great is some of the setup overhead:
public class IterableCodecTest {
//CHECKSTYLE:OFF
@Rule
public final JUnitRuleMockery context = new JUnitRuleMockery();
//CHECKSTYLE:ON
// Mocks
private BSONWriter bsonWriter;
// Under test
private final IterableCodec iterableCodec = new IterableCodec(Codecs.createDefault());
@Before
public void setUp() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setThreadingPolicy(new Synchroniser());
bsonWriter = context.mock(BSONWriter.class);
}
@Test
public void shouldEncodeListOfStrings() {
final List<String> stringList = asList("Uno", "Dos", "Tres");
context.checking(new Expectations() {{
oneOf(bsonWriter).writeStartArray();
oneOf(bsonWriter).writeString("Uno");
oneOf(bsonWriter).writeString("Dos");
oneOf(bsonWriter).writeString("Tres");
oneOf(bsonWriter).writeEndArray();
}});
iterableCodec.encode(bsonWriter, stringList);
}
}
Obviously some of the things there are going to be ringing some people's alarm bells, but let's assume for a minute that all decisions were taken carefully and that pros and cons were weighed accordingly.
So:
- Mocking concrete classes is not pretty in JMock, just look at that
setUp
method. - We're using the
JUnitRuleMockery
, which appears to be Best Practice (and means you're less likely to forget the@RunWith(JMock.class)
annotation), but checkstyle hates it - Public Fields Are Bad as we all know.
But it's fine, a small amount of boilerplate for all our tests that involve mocking is an OK price to pay to have some nice tests.
I converted this test to a Spock test. Groovy purists will notice that it's still very Java-y, and that's intentional - I want these tests, at least at this stage while we're getting used to it, to be familiar to Java programmers, our main audience.
class IterableCodecSpecification extends Specification {
private BSONWriter bsonWriter = Mock();
@Subject
private final IterableCodec iterableCodec = new IterableCodec(Codecs.createDefault());
void 'should encode list of strings'() {
setup:
List<String> stringList = ['Uno', 'Dos', 'Tres'];
when:
iterableCodec.encode(bsonWriter, stringList);
then:
1 * bsonWriter.writeStartArray();
1 * bsonWriter.writeString('Uno');
1 * bsonWriter.writeString('Dos');
1 * bsonWriter.writeString('Tres');
1 * bsonWriter.writeEndArray();
}
}
Some initial observations:
- It's a really simple thing, but I like having the
@Subject
annotation on the thing you're testing. In theory it should be obvious which of your fields or variables is the subject under test, but in practice that's not always true. - Although it freaks me out as someone who's been doing Java for the last 15 years, I really like the String for method name - although in this case it's the same as the JMock/JUnit equivalent, it gives a lot more flexibility for describing the purpose of this test.
- Mocking is painless, with a simple call to
Mock()
, even though we're still mocking concrete classes (this is done simply by adding cglib and obgenesis to the dependencies). - I love that the phases of Spock (
setup: when: then:
) document the different parts of the test while also being the useful magic keywords which tell Spock how to run the test. I know other frameworks provide this, but we've been working with JUnit and I've been in the habit of commenting my steps with//given //when //then
. - Thanks to Groovy, creation of lists is less boiler plate (line 9). Not a big deal, but just makes it easier to read.
- I've got very used to the way expectations are set up in JMock, but I have to say that
1 * bsonWriter.blahblahblah()
is much more readable. - I love that everything after
then:
is an assertion, I think it makes it really clear what you expect to happen after you invoke the thing you're testing.
So mocking is awesome. What's next?