Kennis Flexible methods in JUnit

Flexible methods in JUnit

JUnit default behavior is to only allow methods without arguments. With a little effort you can change JUnit to accept method arguments. When combined with a bit of annotation power, you get great flexible methods with JUnit.

Here is an example of what we want to accomplish. We have lot's of unit tests that read comparison / input data from a file. In the unit test we often need InputStream, Reader or just plain String variables. It would be really nice to get the input passed by just declaring it as a method argument like:

@Test
public void testDocumentFromPluralPoFile(
@TestInput("classpath:nl/avisi/langur/po/plural.po")
Reader reader) {
PoFileParser parser = new PoFileParser();
PoDocument pod = parser.parseDocumentFromStream(reader);
assert...
}

To get this to work we need 2 important components: a custom JUnit test runner and the TestInput annotation.

The component that checks for no-argument test methods is JUnit's default test runner: BlockJUnit4ClassRunner. The first thing to do is to write our own test runner.  To minimize the hard work, we extend BlockJUnit4ClassRunner and change it's behavior at 2 crucial locations:

  • the method validation
  • the actual method invocation.

The method validation should allow for flexible methods and the method invocation is going to be responsible for calling the method with arguments that make some sense. The source code of BlockJUnit4ClassRunner is pretty straightforward and you can easily identify the integration points for your own custom runner. See the results of my go at it in this gist. The methods to override are validateTestMethods and methodInvoker (both annotated with @Override). The TestInput annotation implementation is just a simple marker, the hard work is done in the methodInvoker method.

To use the customer runner you must annotate your test class with the @RunWith annotation:

@RunWith(nl.avisi.langur.test.InputProvidingRunner.class)
public class PoFileImporterTests {
...
}

This gives you an example of how to pass your unit tests simple arguments without worrying about how to get them. You can enhance this example by adding the code that neatly closes the reader / stream or you can add annotations and handling code for other use cases.