Extending JUnit functionality with additional annotation support
I’m currently developing on the Play Framework (1.x), and some of its biggest advantages are its built-in testing framework and automatic support for transactions. One of the downsides of this, however, is that for some tests to work correctly the setup needs to be run inside a separate transaction. Thus you cannot just add extra fixture data at the beginning of the actual test and have it run in the same transaction.
Hence I needed the ability to optionally load extra data within have the setup method, depending on the test being run. JUnit 4.7 added a TestName class which allowed a test (but not @Before methods) to find the name of the test being run, and that was generalised in 4.9 to have a TestWatcher base class, which now ran before the @Before methods.
But having setup perform conditional work depending on the name of the test isn’t the best solution, as the setup could be broken by refactoring the test names.
What I really wanted was to be able to add an annotation to denote the tests which require special work - preferably with the ability to define exactly which special work was required. Fortunately, the TestWatcher provides an easily extensible base for such a class and thus it is the work of five minutes to implement such annotation-driven behaviour:
Firstly, create your custom annotation class, thus:
@Retention( value = RetentionPolicy.RUNTIME)
@Target( value = { ElementType.METHOD})
public @interface Marker {
public String value() default "";
}
Then create your own TestWatcher, which gets filled in by JUnit with any Markers annotated against the test method being executed:
public class TestAnnotated extends TestWatcher {
private Marker marker;
@Override
protected void starting( Description description) {
marker = description.getAnnotation( Marker.class);
}
public Marker getMarker() {
return marker;
}
/** @return the name field attached to the marker.
Null if no annotation, "" if no value field. */
public String getMarkerValue() {
if( marker == null) {
return null;
}
return marker.value();
}
}
The value parameter in the annotation can be used to specify extra information about the test.
And that’s it. Here is how you use it in your tests:
public class SomeTest {
private static final String LOAD_SPECIAL_DATA
= "Load Special Data";
@Rule
public TestAnnotated annotated = new TestAnnotated();
@Before
public void setup() {
// load basic fixtures
if( null != annotated.getMarker()) {
// do extra setup for all marked tests
}
if( LOAD_SPECIAL_DATA.equals( annotated.getMarkerValue())) {
// do special setup just for those tests
}
}
@Test
public void ordinaryTest() {
// just standard fixtures for this test
}
@Test
@Marker
public void testRequiringExtraData() {
// extra data will be loaded for this test
}
@Test
@Marker( LOAD_SPECIAL_DATA)
public void testRequiringSpecialData() {
// both extra data and special data
// will be loaded for this test
}
}
