martiansnoop
martiansnoop

Reputation: 2326

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?

Upvotes: 6

Views: 2953

Answers (4)

Brandon - Rearden
Brandon - Rearden

Reputation: 366

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

Upvotes: 10

NamshubWriter
NamshubWriter

Reputation: 24276

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
  }
}

Upvotes: 0

Stefan Birkner
Stefan Birkner

Reputation: 24510

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.

Upvotes: 0

cjstehno
cjstehno

Reputation: 13984

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.

Upvotes: 1

Related Questions