Jens Schauder is software developer since 1997. He loves software development for the constant challenges and constantly changing environment. A great chance to learn and teach. He is also blogger, author of various articles and speaker at conferences. Jens is a DZone MVB and is not an employee of DZone and has posted 86 posts at DZone. You can read more from them at their website. View Full User Profile

Rules in JUnit 4.9 (beta 3)

07.25.2011
| 47663 views |
  • submit to reddit

Some time ago David Saff announced a beta release of JUnit 4.9. So I guess it is a good point in time to look into what is new in this version.

One of the most useful innovations in the JUnit realm have been Rules. I wrote about Rules here. And I wrote about use cases for JUnit Rules here. Rules are great. And with JUnit 4.9 they get even better.

You can think of Rules as a way to encapsulate setup and teardown of a test in one class instead of two methods. But Rules are also a way to modify the way to execute your tests. You can run tests a dozen time instead of once. Or in twenty different threads. Interestingly there were only Rules for single tests. So if you want to stick with the comparison with setup and teardown, aka @Before and @After there wasn’t a @BeforeClass and @AfterClass equivalent in Rules.

Now that has changed. You can now annotate a public static field of type TestRule with the @ClassRule and it will behave just like a Rule that is defined for a whole test class instead of a single test. So it is perfect for the stuff that needs setup once for all the tests instead of for each tests. Lets look at an example.

The implementation of a Rule might look like this:

import org.junit.rules.TestRule;

    import org.junit.runner.Description;
    import org.junit.runners.model.Statement;
     
    public class LoggingRule implements TestRule {
     
     public class LoggingStatement extends Statement {
     
      private final Statement statement;
     
      public LoggingStatement(Statement aStatement, String aName) {
       statement = aStatement;
      }
     
      @Override
      public void evaluate() throws Throwable {
       System.out.println("before: " + name);
       statement.evaluate();
       System.out.println("after: " + name);
      }
     
     }
     
     private final String name;
     
     public LoggingRule(String aName) {
      name = aName;
     }
     
     @Override
     public Statement apply(Statement statement, Description description) {
      System.out.println("apply: " + name);
     
      return new LoggingStatement(statement, name);
     }
     
    }

Most implementations will consist of two parts: An implementation of the TestRule interface and an implementation of the Statement interface.

TestRule replaces the now deprecated MethodRule interface which was used before. This is because the new interface supports both Rules on class level and on method level, so it had to change a little. TestRule has a single method apply which takes a Statement and returns a Statement. This method gets called before any test in the scope of the Rule gets executed. The Statement passed in actually are the tests that might get executed. Two things to note here: The Statement might and will represent multiple tests, if your Rule gets used with a @ClassRule annotation; And the call to apply doesn’t mean the Statement will actually get executed. Since whatever your Rule returns might get passed to other Rules, the Statements might got mangled in various ways before the contained tests get actually executed. The typical thing todo in the apply method is to wrap the Statement in a new Statement which will perform what ever logic is needed for your purpose.

The Statement interface has a single method evaluate which should execute a test or a bunch of tests in the normal case. So if you go with the typical approach mentioned above you do some setup call evaluate of the contained Statement and do some tear down. In the example provided above I print stuff on the console so one can see in which order stuff gets called. The Statement also gets passed a Description which contains useful meta information about the test(s). It contains the name, the class in which the test is defined, the method name and makes annotations available. So your Rule/Statement can fine tune its behavior based on the test method on which it operates.

A test class using this Rule might look like this:

import org.junit.ClassRule;

    import org.junit.Rule;
    import org.junit.Test;
     
    public class RuleTest {
     
     @ClassRule
     public static LoggingRule classRule = new LoggingRule("classrule");
     
     @Rule
     public static LoggingRule rule = new LoggingRule("rule");
     
     @Test
     public void testSomething() {
      System.out.println("In TestSomething");
      assertTrue(true);
     }
     
     @Test
     public void testSomethingElse() {
      System.out.println("In TestSomethingElse");
      assertTrue(true);
     }
    }

The only change to JUnit4.8 Rules is the presence of the @ClassRule annotation. Note that the same class is used with @ClassRule and @Rule annotation.

When executed the output of the test class looks like this:

apply: classrule
before: classrule
apply: rule
before: rule
In TestSomething
after: rule
apply: rule
before: rule
In TestSomethingElse
after: rule
after: classrule

As you can see first the class level Rule gets applied and the resulting Statement evaluated. Only as part of the evaluation does the method level Rule applied and the resulting Statement evaluated, once for each test.

One word of caution: Be careful to use the correct modifiers with your Rules. They must be public and class level Rules must be static. Depending on what you do wrong (and in what environment you are working in) the resulting errors might not be exactly helpful. This is considered a bug and fixes are in the making.

 

From http://blog.schauderhaft.de/2011/07/24/rules-in-junit-4-9-beta-3/

Published at DZone with permission of Jens Schauder, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)