1. Overview
In this tutorial, we’re going to take a look at the Rules feature provided by the JUnit 4 library.
We’ll begin by introducing the JUnit Rules Model before walking through the most important base rules provided by the distribution. Additionally, we’ll also see how to write and use our own custom JUnit Rule.
To learn more about testing with JUnit, check out our comprehensive JUnit series.
Note that if you’re using JUnit 5, rules have been replaced by the Extension model.
2. Introduction to JUnit 4 Rules
JUnit 4 rules provide a flexible mechanism to enhance tests by running some code around a test case execution. In some sense, it’s similar to having @Before and @After annotations in our test class.
Let’s imagine we wanted to connect to an external resource such as a database during test setup and then close the connection after our test finishes. If we want to use that database in multiple tests, we’d end up duplicating that code in every test.
By using a rule, we can have everything isolated in one place and reuse the code easily from multiple test classes.
3. Using JUnit 4 Rules
So how can we use rules? We can use JUnit 4 rules by following these simple steps:
- Add a public field to our test class and ensure that the type of this field is a subtype of the org.junit.rules.TestRule interface
- Annotate the field with the @Rule annotation
In the next section, we’ll see what project dependencies we need to get started.
4. Maven Dependencies
First, let’s add the project dependencies we’ll need for our examples. We’ll only need the main JUnit 4 library:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
As always, we can get the latest version from Maven Central.
5. Rules Provided in the Distribution
Of course, JUnit provides a number of useful, predefined rules as part of the library. We can find all these rules in the org.junit.rules package.
In this section, we’ll see some examples of how to use them.
5.1. The TemporaryFolder Rule
When testing, we often need access to a temporary file or folder. However, managing the creation and deletion of these files can be cumbersome. Using the TemporaryFolder rule, we can manage the creation of files and folders that should be deleted when the test method terminates:
@Rule
public TemporaryFolder tmpFolder = new TemporaryFolder();
@Test
public void givenTempFolderRule_whenNewFile_thenFileIsCreated() throws IOException {
File testFile = tmpFolder.newFile("test-file.txt");
assertTrue("The file should have been created: ", testFile.isFile());
assertEquals("Temp folder and test file should match: ",
tmpFolder.getRoot(), testFile.getParentFile());
}
As we can see, we first define the TemporaryFolder rule tmpFolder. Next, our test method creates a file called test-file.txt in the temporary folder. We then check that the file has been created and exists where it should. Really nice and simple!
When the test finishes, the temporary folder and file should be deleted. However, this rule doesn’t check whether or not the deletion is successful.
There are also a few other interesting methods worth mentioning in this class:
newFile()
If we don’t provide any file name, then this method creates a randomly named new file.
newFolder(String... folderNames)
To create recursively deep temporary folders, we can use this method.
newFolder()
Likewise, the newFolder() method creates a randomly named new folder.
A nice addition worth mentioning is that starting with version 4.13, the TemporaryFolder rule allows verification of deleted resources:
@Rule
public TemporaryFolder folder = TemporaryFolder.builder().assureDeletion().build();
If a resource cannot be deleted, the test with fail with an AssertionError.
Finally, in JUnit 5, we can achieve the same functionality using the Temporary Directory extension.
5.2. The ExpectedException Rule
As the name suggests, we can use the ExpectedException rule to verify that some code throws an expected exception:
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void givenIllegalArgument_whenExceptionThrown_MessageAndCauseMatches() {
thrown.expect(IllegalArgumentException.class);
thrown.expectCause(isA(NullPointerException.class));
thrown.expectMessage("This is illegal");
throw new IllegalArgumentException("This is illegal", new NullPointerException());
}
As we can see in the example above, we’re first declaring the ExpectedException rule. Then, in our test, we’re asserting that an IllegalArgumentException is thrown.
Using this rule, we can also verify some other properties of the exception, such as the message and cause.
For an in-depth guide to testing exceptions with JUnit, check out our excellent guide on how to Assert an Exception.
5.3. The TestName Rule
Put simply, the TestName rule provides the current test name inside a given test method:
@Rule public TestName name = new TestName();
@Test
public void givenAddition_whenPrintingTestName_thenTestNameIsDisplayed() {
LOG.info("Executing: {}", name.getMethodName());
assertEquals("givenAddition_whenPrintingTestName_thenTestNameIsDisplayed", name.getMethodName());
}
In this trivial example, when we run the unit test, we should see the test name in the output:
INFO c.baeldung.rules.JUnitRulesUnitTest -
Executing: givenAddition_whenPrintingTestName_thenTestNameIsDisplayed
5.4. The Timeout Rule
In this next example, we’ll take a look at the Timeout rule. This rule offers a useful alternative to using the timeout parameter on an individual Test annotation.
Now, let’s see how to use this rule to set a global timeout on all the test methods in our test class:
@Rule
public Timeout globalTimeout = Timeout.seconds(10);
@Test
public void givenLongRunningTest_whenTimout_thenTestFails() throws InterruptedException {
TimeUnit.SECONDS.sleep(20);
}
In the above trivial example, we first define a global timeout for all test methods of 10 seconds. Then we deliberately define a test which will take longer than 10 seconds.
When we run this test, we should see a test failure:
org.junit.runners.model.TestTimedOutException: test timed out after 10 seconds
...
5.5. The ErrorCollector Rule
Next up we’re going to take a look at the ErrorCollector rule. This rule allows the execution of a test to continue after the first problem is found.
Let’s see how we can use this rule to collect all the errors and report them all at once when the test terminates:
@Rule
public final ErrorCollector errorCollector = new ErrorCollector();
@Test
public void givenMultipleErrors_whenTestRuns_thenCollectorReportsErrors() {
errorCollector.addError(new Throwable("First thing went wrong!"));
errorCollector.addError(new Throwable("Another thing went wrong!"));
errorCollector.checkThat("Hello World", not(containsString("ERROR!")));
}
In the above example, we add two errors to the collector. When we run the test, the execution continues, but the test will fail at the end.
In the output, we will see both errors reported:
java.lang.Throwable: First thing went wrong!
...
java.lang.Throwable: Another thing went wrong!
5.6. The Verifier Rule
The Verifier rule is an abstract base class that we can use when we wish to verify some additional behavior from our tests. In fact, the ErrorCollector rule we saw in the last section extends this class.
Let’s now take a look at a trivial example of defining our own verifier:
private List messageLog = new ArrayList();
@Rule
public Verifier verifier = new Verifier() {
@Override
public void verify() {
assertFalse("Message Log is not Empty!", messageLog.isEmpty());
}
};
Here, we define a new Verifier and override the verify() method to add some extra verification logic. In this straightforward example, we simply check to see that the message log in our example isn’t empty.
Now, when we run the unit test and add a message, we should see that our verifier has been applied:
@Test
public void givenNewMessage_whenVerified_thenMessageLogNotEmpty() {
// ...
messageLog.add("There is a new message!");
}
5.7. The DisableOnDebug Rule
Sometimes we may want to disable a rule when we’re debugging. For example, it’s often desirable to disable a Timeout rule when debugging to avoid our test timing out and failing before we’ve had time to debug it properly.
The DisableOnDebug Rule does precisely this and allows us to label certain rules to be disabled when debugging:
@Rule
public DisableOnDebug disableTimeout = new DisableOnDebug(Timeout.seconds(30));
In the example above we can see that in order to use this rule, we simply pass the rule we want to disable to the constructor.
The main benefit of this rule is that we can disable rules without making any modifications to our test classes during debugging.
5.8. The ExternalResource Rule
Typically, when writing integration tests, we may wish to set up an external resource before a test and tear it down afterward. Thankfully, JUnit provides another handy base class for this.
We can extend the abstract class ExternalResource to set up an external resource before a test, such as a file or a database connection. In fact, the TemporaryFolder rule we saw earlier extends ExternalResource.
Let’s take a quick look at how we could extend this class:
@Rule
public final ExternalResource externalResource = new ExternalResource() {
@Override
protected void before() throws Throwable {
// code to set up a specific external resource.
};
@Override
protected void after() {
// code to tear down the external resource
};
};
In this example, when we define an external resource we simply need to override the before() method and after() method in order to set up and tear down our external resource.
6. Applying Class Rules
Up until now, all the examples we’ve looked at have applied to single test case methods. However, sometimes we might want to apply a rule at the test class level. We can accomplish this by using the @ClassRule annotation.
This annotation works very similarly to @Rule but wraps a rule around a whole test — the main difference being that the field we use for our class rule must be static:
@ClassRule
public static TemporaryFolder globalFolder = new TemporaryFolder();
7. Defining a Custom JUnit Rule
As we’ve seen, JUnit 4 provides a number of useful rules out of the box. Of course, we can define our own custom rules. To write a custom rule, we need to implement the TestRule interface.
Let’s take a look at an example of defining a custom test method name logger rule:
public class TestMethodNameLogger implements TestRule {
private static final Logger LOG = LoggerFactory.getLogger(TestMethodNameLogger.class);
@Override
public Statement apply(Statement base, Description description) {
logInfo("Before test", description);
try {
return new Statement() {
@Override
public void evaluate() throws Throwable {
base.evaluate();
}
};
} finally {
logInfo("After test", description);
}
}
private void logInfo(String msg, Description description) {
LOG.info(msg + description.getMethodName());
}
}
As we can see, the TestRule interface contains one method called apply(Statement, Description) that we must override to return an instance of Statement. The statement represents our tests within the JUnit runtime. When we call the evaluate() method, this executes our test.
In this example, we log a before and after message and include from the Description object the method name of the individual test.
8. Using Rule Chains
In this final section, we’ll take a look at how we can order several test rules using the RuleChain rule:
@Rule
public RuleChain chain = RuleChain.outerRule(new MessageLogger("First rule"))
.around(new MessageLogger("Second rule"))
.around(new MessageLogger("Third rule"));
In the above example, we create a chain of three rules that simply print out the message passed to each MessageLogger constructor.
When we run our test, we’ll see how the chain is applied in order:
Starting: First rule
Starting: Second rule
Starting: Third rule
Finished: Third rule
Finished: Second rule
Finished: First rule
9. Conclusion
To summarize, in this tutorial, we’ve explored JUnit 4 rules in detail.
First, we started by explaining what rules are and how we can use them. Next, we took an in-depth look at the rules that come as part of the JUnit distribution.
Finally, we looked at how we can define our own custom rule and how to chain rules together.
As always, the full source code of the article is available over on GitHub.