Apologies for the shouty title, I couldn’t stop myself. In JUnit 4.7 a new feature was introduced – rules. They enable us to modify how test methods are executed to provide more flexible and powerful tests. I think the name was chosen so that people would have to search for “junit rules” and google trends seem to (weakly) support this theory. In your face TestNG!

The functionality is implemented by annotating a field (of class TestRule or MethodRule) with the @Rule annotation.
This is a quick tour of the most useful rules.
What’s your name again?
Would you like to know your test method’s name while executing it? Simple, use TestName.
@Rule public TestName name = new TestName(); @Test public void printTestMethodName() { System.out.println( "Test method name: " + name.getMethodName()); }
Test method name: printTestMethodName
We want quick tests!
Quick as in “not slow”. Let’s say you need to deal with a class full of badly written integration tests, each of which unnecessarily taking several seconds to run. Bad bad panda. You can set up a timeout (Timeout) in your test class which will fail lengthy tests (just note you’re dealing with milliseconds). Another good thing is that it will actually interrupt the test execution thread so your test method will be time-boxed by the timeout.
@Rule public Timeout timeout = new Timeout(20); @Test public void slowTestAskingForTrouble() throws InterruptedException { Thread.sleep(1000 * 60); }
java.lang.Exception: test timed out after 20 milliseconds
Exceptions expected
Sometimes you want to check if an exception is thrown. You can do it without the rules but then you can’t check for the message.
@Test(expected = IllegalArgumentException.class) public void exceptionExpected(){ throw new IllegalArgumentException("catch me if you can"); }
Exception messages are important. Sometimes they can tell the whole story, i.e. would you prefer to simply catch UserNotFoundException or would you also like to see the message “Can not load user with id ‘null'” in the production log? Right, let’s check for the exception message with ExpectedException.
@Rule public ExpectedException thrown = ExpectedException.none(); @Test public void exceptionWithMessageExpected(){ thrown.expect(IllegalArgumentException.class); thrown.expectMessage("catch me if you can"); throw new IllegalArgumentException("catch me if you can"); }
This rule is initialised with ExpectedException.none() as by default we don’t expect methods to end up with an exception. We state this behaviour as per method basis.
Note that you can use Timeout and ExpectedException together and have a passing test
@Test public void slowTestWithExpectedException() throws InterruptedException { thrown.expect(Exception.class); thrown.expectMessage( "test timed out after 20 milliseconds"); Thread.sleep(1000 * 60); }
Collect your errors (all of them)
Sometimes you end up having several assertions to verify that the returned object is ok. Let’s say you load a user object and the first assertion is for user’s first name and it fails. Fix it, run again to discover that the date of birth assertion fails too. Repeat to discover that their work phone number is empty (who uses these anyway?)… Wouldn’t it be nice to find out all failing assertions after the first execution? This is when ErrorCollector comes in silver armour and on a white horse to rescue the situation.
@Rule public ErrorCollector collector = new ErrorCollector(); @Test public void errorCollectorExample() { collector.addError( new Throwable("trouble here")); collector.addError( new Throwable("trouble there")); collector.addError( new Throwable("trouble everywhere")); }
java.lang.Throwable: trouble here
at com.czeczotka.junit.JUnitRulesTest.errorCollectorExample(JUnitRulesTest.java:52)
java.lang.Throwable: trouble there
at com.czeczotka.junit.JUnitRulesTest.errorCollectorExample(JUnitRulesTest.java:53)
java.lang.Throwable: trouble everywhere
at com.czeczotka.junit.JUnitRulesTest.errorCollectorExample(JUnitRulesTest.java:54)
Watch your tests
Should you be interested in being able to get you hands on test’s lifecycle events you can use TestWatcher. It will not let you change the test but you can register callbacks for different events such as failed, finished, skipped, starting, succeeded. Useful for reporting or if you wanted to take some extra actions, i.e. feedback the problem to you continuous delivery pipeline.
private static StringBuilder report = new StringBuilder(); @Rule public TestWatcher watcher = new TestWatcher() { @Override protected void failed(Throwable e, Description description) { report.append(" FAILURE: ").append( description.getMethodName()).append("\n"); } @Override protected void succeeded(Description description) { report.append(" Success: ").append( description.getMethodName()).append("\n"); } }; @AfterClass public static void tearDownClass() { System.out.println("@AfterClass report"); System.out.println(report.toString ()); }
@AfterClass report Success: exceptionExpected Success: printTestMethodName FAILURE: errorCollectorExample Success: slowTestWithExpectedException Success: exceptionWithMessageExpected FAILURE: slowTestAskingForTrouble
Other useful stuff
- TemporaryFolder – enables you to create files and directories (newFile(), newFolder() methods) that will be removed after test execution
- ExternalResource – useful to access an external resource such as a database or ssh connection (methods before() and after()) but I think I’d prefer to use BeforeClass and AfterClass annotations to do the job
- Verifier – to potentially fail a test which would otherwise be successful after some extra assertions, i.e.: temporary folder was too big (in conjunction with TemporaryFolder rule)
The code
The code of this post is on github so follow the link below to browse it. Note that it contains failing tests to demonstrate presented ideas.
http://github.com/czeczotka/junit-rules
You can easily clone and play with it locally by simply following these steps.
$ git clone https://github.com/czeczotka/junit-rules.git $ cd junit-rules $ mvn test