Combining @ClassRule and @Rule in JUnit 4.11
Asked Answered
T

5

22

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

Tattler answered 24/12, 2013 at 22:43 Comment(1)
See also JUnit issue #793 on GitHub, where I've asked this same question.Tattler
T
14

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.

Tattler answered 19/6, 2014 at 6:30 Comment(1)
This was released as part of JUnit 4.12Bagnio
T
6

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.

Tattler answered 25/12, 2013 at 12:47 Comment(3)
In the Rule's code, how do you differentiate when it fires as a @ClassRule (set up the external resource in your example) and when it fires as a @Rule (resetting the resource in that example)?Principium
It'll depend on the exact implementation of the rule. In my case, the external resource can be interrogated to see if it's up and running. If it is, we're acting as a @Rule, so we run the base Statement and then reset. If not, we're acting as a @ClassRule, so we set up the resource, run the base Statement and then close down the resource.Tattler
The Description passed into TestRule can be checked to see if the current node is a test (i.e. no children) or a suite (at least one child)Bagnio
T
2

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.

Tattler answered 26/12, 2013 at 11:17 Comment(0)
U
1

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.

Unfurl answered 31/12, 2013 at 20:43 Comment(0)
M
0

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.

Moderate answered 26/12, 2013 at 15:54 Comment(2)
My use case is basically the example I gave in the question: I have an external resource (a WireMock instance, to be specific) that I'd like to set up & tear down at class start / end, and reset in between tests.Tattler
I think it's not possible to configure it using one rule. Based on use case that you've published on GitHub I would suggest put these rules into some base class (to do not allow developers to forget about this and not to duplicate code)Moderate

© 2022 - 2024 — McMap. All rights reserved.