Tag Archives: rule

JUnit rules!

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!

google-trends-for-junit-rules
Google Trends for “junit rules” search term

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

Links