How to combine @Rule and @ClassRule in JUnit 4.12
Asked Answered
R

4

6

According to the 4.12 release notes, it is possible to annotate static members of a test class with both @Rule and @ClassRule:

a static member annotated with both @Rule and @ClassRule is now considered valid. This means a single rule may be used to perform actions both before/after a class (e.g. setup/tear down an external resource) and between tests (e.g. reset the external resource),

I want to use this functionality to initialize a resource at the beginning of all tests in the file, do some cleanup on the resource between each test, and dispose of it after all tests have finished. This resource is currently represented by a class that extends ExternalResource.

In my before and after methods, how can I differentiate between "before/after all tests" and "before/after each test"? Do I need to use a different/custom implementation of TestRule to accomplish this?

Razee answered 3/9, 2015 at 19:49 Comment(1)
@AjitSingh that article does not explain how to combine the behaviour of @ Rule and @ ClassRule in one class implementing TestRule.Razee
V
10

You can implement TestRule#apply and use the isTest and isSuite methods of Description to determine what kind of Statement your TestRule is being applied to.

Here's an example interface you could build to give a rule that has full before, after, verify, beforeClass, afterClass, verifyClass type behavior:

public interface CombinedRule extends TestRule {
    default Statement apply(Statement base, Description description) {
        if (description.isTest()) {
            return new Statement() {
                public void evaluate() throws Throwable {
                    before();
                    try {
                        base.evaluate();
                        verify();
                    } finally {
                        after();
                    }
                }
            };
        }
        if (description.isSuite()) {
            return new Statement() {
                public void evaluate() throws Throwable {
                    beforeClass();
                    try {
                        base.evaluate();
                        verifyClass();
                    } finally {
                        afterClass();
                    }
                }
            };
        }
        return base;
    }

    default void before() throws Exception {
        //let the implementer decide whether this method is useful to implement
    }

    default void after() {
        //let the implementer decide whether this method is useful to implement
    }

    /**
     * Only runs for Tests that pass
     */
    default void verify() {
        //let the implementer decide whether this method is useful to implement
    }

    default void beforeClass() throws Exception {
        before();
    }

    default void afterClass() {
        after();
    }

    /**
     * Only runs for Suites that pass
     */
    default void verifyClass() {
        verify();
    }
}
Voodoo answered 13/2, 2018 at 4:40 Comment(0)
A
1

The methods annotated with @Before and @After will be run before and after each test, while those annotated with @BeforeClass and @AfterClass will be run before and after the first/last test in the class respectively.

The before/after methods of a @Rule are executed before and after each test, while the before/after methods of a @ClassRule are run before/after the whole test class.

You can use an ExternalResource for either the @Rule or @ClassRule case as long as the handler methods react properly for both scenarios. As far as I can tell from the documentation, there is no means of distinguishing between the two rule categories within the rule class methods. If you use the rule class for both cases, it will be applied the same for both.

Adermin answered 3/9, 2015 at 20:5 Comment(2)
Ok, so if @ClassRule and @Rule are on the same static member, its before/after methods will be run before/after the whole class and before/after each test? That doesn't seem useful since it doesn't differentiate between the expensive setup and teardown of a resource and resetting (without full teardown) it between tests. Do you know why the junit team decided that was useful? Is the answer to use separate @Rule and @ClassRule members like in the blog you linked?Razee
I would imagine someone came up with a valid use case, so they implemented it. It would be nice if they pointed out that use case. :-) Yes, I would suggest splitting them up as in my blog entry (coffeaelectronica.com/blog/2013/junit-rules.html)Adermin
C
0

You cannot differentiate between @BeforeClass and @Before or @AfterClass and @After. More details about the reason for adding this feature may be found in the pull request.

Clack answered 4/9, 2015 at 12:48 Comment(0)
M
0

You can create your own rule that implements both TestRule and MethodRule:

public SharableExternalResource implements TestRule, MethodRule {

  public final Statement apply(
      final Statement base, Description description) {
    return
        new Statement() {
          @Override
          public void evaluate() throws Throwable {
            beforeClass();

            List<Throwable> errors = new ArrayList<Throwable>();
            try {
                base.evaluate();
            } catch (Throwable t) {
                errors.add(t);
            } finally {
                try {
                    afterClass();
                } catch (Throwable t) {
                    errors.add(t);
                }
            }
            MultipleFailureException.assertEmpty(errors);
          }
        };
  }

  public final Statement apply(
      Statement base, FrameworkMethod method, Object target) {
    return
        new Statement() {
          @Override
          public void evaluate() throws Throwable {
            before();

            List<Throwable> errors = new ArrayList<Throwable>();
            try {
                base.evaluate();
            } catch (Throwable t) {
                errors.add(t);
            } finally {
                try {
                    after();
                } catch (Throwable t) {
                    errors.add(t);
                }
            }
            MultipleFailureException.assertEmpty(errors);
          }
        };
  }

  public void beforeClass() throws Exception {
    // do nothing
  }

  protected void before() throws Exception {
    // do nothing
  }

  protected void after() throws Exception {
    // do nothing
  }

  public void afterClass() throws Exception {
    // do nothing
  }
}
Mohamed answered 22/4, 2017 at 5:35 Comment(3)
The documentation for MethodRule states "Note that MethodRule has been replaced by TestRule, which has the added benefit of supporting class rules", which seems to imply that MethodRule is deprecated in favor of TestRule. I haven't ran your code, so I can't say for sure, but it seems odd that TestRule would ignore an @Rule annotation if the rule in question also implements MethodRule.Razee
@Razee MethodRile is no longer deprecated. I suggest trying before assuming it won't workMohamed
The solution posted here is invalid because MethodRule cannot be static, but ClassRule must be static. See github.com/junit-team/junit4/blob/…Razee

© 2022 - 2024 — McMap. All rights reserved.