Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 966
martiansnoop Avatar asked Sep 03 '15 19:09

martiansnoop


2 Answers

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();
    }
}
like image 138
Brandon Avatar answered Oct 03 '22 12:10

Brandon


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.

like image 41
cjstehno Avatar answered Oct 03 '22 11:10

cjstehno