I have a class that is a JUnit suite of JUnit test classes. I would like to define a rule on the suite to do something to the database before and after each unit test is run if a certain annotation is present on that test method.
I've been able to create a @ClassRule
in the suites and test classes that will do this before each and every class (which is not good enough) and I have been able to define the test rules with the test classes themselves, but this is repetitive and does not seem very DRY.
Is it possible to define a per-test-method rule in the suite or must I add them to each and every test?
Edit: To clarify, I want to declare code in a suite that will run between (i.e. "around") the test methods in the test classes.
This can be done, but it needs a bit of work. You need to define your own Suite runner and your own Test runner as well, and then override runChild() in the test runner. Using the following:
AllTests.java:
@RunWith(MySuite.class)
@SuiteClasses({Class1Test.class})
public class AllTests {
}
Class1Test.java:
public class Class1Test {
@Deprecated @Test public void test1() {
System.out.println("" + this.getClass().getName() + " test1");
}
@Test public void test2() {
System.out.println("" + this.getClass().getName() + " test2");
}
}
Note that I've annotated test1()
with @Deprecated
. You want to do something different when you have the @Deprecated
annotation on the test, so we need to extend Suite to use a custom Runner
:
public class MySuite extends Suite {
// copied from Suite
private static Class<?>[] getAnnotatedClasses(Class<?> klass) throws InitializationError {
Suite.SuiteClasses annotation = klass.getAnnotation(Suite.SuiteClasses.class);
if (annotation == null) {
throw new InitializationError(String.format("class '%s' must have a SuiteClasses annotation", klass.getName()));
}
return annotation.value();
}
// copied from Suite
public MySuite(Class<?> klass, RunnerBuilder builder) throws InitializationError {
super(null, getRunners(getAnnotatedClasses(klass)));
}
public static List<Runner> getRunners(Class<?>[] classes) throws InitializationError {
List<Runner> runners = new LinkedList<Runner>();
for (Class<?> klazz : classes) {
runners.add(new MyRunner(klazz));
}
return runners;
}
}
JUnit creates a Runner
for each test it will run. Normally, Suite would just create the default BlockJUnit4ClassRunner
, all we're doing here is overriding the constructor for the Suite which reads the classes from the SuiteClass
annotation and we're creating our own runners with them, MyRunner
. This is our MyRunner class:
public class MyRunner extends BlockJUnit4ClassRunner {
public MyRunner(Class<?> klass) throws InitializationError {
super(klass);
}
@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description= describeChild(method);
if (method.getAnnotation(Ignore.class) != null) {
notifier.fireTestIgnored(description);
} else {
if (description.getAnnotation(Deprecated.class) != null) {
System.out.println("name=" + description.getMethodName() + " annotations=" + description.getAnnotations());
}
runLeaf(methodBlock(method), description, notifier);
}
}
}
Most of this is copied from BlockJUnit4ClassRunner
. The bit I've added is:
if (description.getAnnotation(Deprecated.class) != null) {
System.out.println("name=" + description.getMethodName() + " annotations=" + description.getAnnotations());
}
where we test for the existence of the @Deprecated
annotation on the method, and do something if it's there. The rest is left as an exercise for the reader. When I run the above Suite, I get as output:
name=test1 annotations=[@java.lang.Deprecated(), @org.junit.Test(expected=class org.junit.Test$None, timeout=0)]
uk.co.farwell.junit.run.Class1Test test1
uk.co.farwell.junit.run.Class1Test test2
Please note that Suite has multiple constructors depending upon how it is invoked. The above works with Eclipse, but I haven't tested other ways of running the Suite. See the comments alongside the various constructors for Suite for more information.