Search code examples
javajunitjunit4junit-rule

How to combine @Rule and @ClassRule in JUnit 4.12


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?


Solution

  • 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();
        }
    }