Question

In JUnit 4.10 and below, it is possible to annotate a rule as both @Rule and @ClassRule. This means the rule gets invoked before before/after the class, and before/after each test. One possible reason for doing so is to set up an expensive external resource (via the @ClassRule calls) and then cheaply reset it (via the @Rule calls).

As of JUnit 4.11, @Rule fields must be non-static and @ClassRule fields must be static, so the above is no longer possible.

There are clearly workarounds (e.g. explicitly separate the @ClassRule and @Rule responsibilities into separate rules), but it seems a shame to have to mandate the use of two rules. I briefly looked at using @Rule and inferring whether or not its the first / last test, but I don't believe that information is available (at least, it's not directly available on Description).

Is there a neat and tidy way of combining the @ClassRule and @Rule functionality in a single rule in JUnit 4.11?

Thanks, Rowan

Was it helpful?

Solution

As of JUnit 4.12 (unreleased at time of writing), it will be possible to annotate a single static rule with both @Rule and @ClassRule.

Note that it must be static - a non-static rule annotated with @Rule and @ClassRule is still considered invalid (as anything annotated @ClassRule works at the class level, so only really makes sense as a static member).

See the release notes and my pull request if you're interested in more detail.

OTHER TIPS

Another possible workaround is to declare a static @ClassRule and use that value when declaring a non-static @Rule, too:

@ClassRule
public static BeforeBetweenAndAfterTestsRule staticRule = new BeforeBetweenAndAfterTestsRule();
@Rule
public BeforeBetweenAndAfterTestsRule rule = staticRule;

That means you don't have to refactor any existing rule classes, but you still need to declare two rules, so it doesn't answer the original question particularly well.

Yet another possible workaround is to declare a non-static @Rule, and have it act on static collaborators: if the collaborator isn't initialised yet, the @Rule knows it's running for the first time (so it can set up its collaborators, e.g. starting an external resource); if they are initialised, the @Rule can do it's per-test work (e.g. reseting an external resource).

This has the drawback that the @Rule doesn't know when it's processed the last test, so can't perform any after-class actions (e.g. tidying up the external resource); an @AfterClass method can be used to do so, instead, however.

The answer for you question is following: there is no clean way to do it (only setup two rules simultaneously). We tried to implement similar task for automatic tests retries, and two rules were combined (for this task) and such ugly approach was implemented: Tests retry with two rules

But if think more precisely on the necessary task (that needs to be implemented) better approach can be used using jUnit custom Runner: Retry Runner.

So, in order to have better approach it will be good to know your specific use case.

I've experienced similar problems there are two work arounds. I don't like either one but they have different trade-offs:

1) If your Rule exposes clean up methods, you can manually invoke the clean up inside a @Before method.

@ClassRule
public static MyService service = ... ;

@Before
public void cleanupBetweenTests(){
     service.cleanUp();
}

The downside to this is you need to remember (and to tell others on your team) to always add that @Before method or create an abstract class that your tests inherit from to do the clean up for you.

2) Have 2 fields, one static, one non-static that point to the same object, each field annotated by either a @ClassRule or @Rule respectively. This is needed if the cleanup isn't exposed. The downside of course is you also have to remember to have both a @ClassRule and a @Rule point to the same thing which looks strange.

 @ClassRule
 public static MyService service = ... ;

@Rule
public MyService tmp = service ;

Then in your implementation you have to differentiate between a test suite or a single test. This can be done by checking if the Description has any children. Depending on which one, you create different Statement adapters to handle cleanup or not:

@Override
protected void after() {

    //class-level shut-down service
    shutdownService();
}


@Override
protected void before() {

    //class-level init service
    initService();
}

@Override
public Statement apply(Statement base, Description description) {

        if(description.getChildren().isEmpty()){
            //test level perform cleanup

            return new CleanUpStatement(this,base);
        }
        //suite level no-change
        return super.apply(base, description);

    }

Here is the custom Statement class to clean up before each test:

private static final class CleanUpStatement extends Statement{
        private final MyService service;

        private final Statement statement;



        CleanUpStatement(MyService service, Statement statement) {
            this.service = service;
            this.statement = statement;
        }



        @Override
        public void evaluate() throws Throwable {
            //clear messages first
            myService.cleanUp();
            //now evaluate wrapped statement
            statement.evaluate();
        }

    }

After all of that, I would lean more towards option 1 since it's more intent revealing and is less code to maintain. I would also worry about others trying to modify the code in option 2 thinking there was a bug since the same field is pointed to twice. The extra effort, code comments etc are not worth it.

Eitherway you still have boiler plate to copy and paste everywhere or abstract classes using a template method.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top