When trying to make a temporary file for unit tests I came across this answer that mentioned "TemporaryFolder JUnit @Rule" and a link explaining how to use it. Which is like this:
@Rule
public TemporaryFolder testFolder = new TemporaryFolder();
and then testFolder.newFile("file.txt")
My question is what does the @Rule annotation do?
Removing the annotation doesn't appear to actually change anything.
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.
Use @ClassRule to set up something that can be reused by all the test methods, if you can achieve that in a static method. Use @Rule to set up something that needs to be created a new, or reset, for each test method.
A TestRule is an alteration in how a test method, or set of test methods, is run and reported. A TestRule may add additional checks that cause a test that would otherwise fail to pass, or it may perform necessary setup or cleanup for tests, or it may observe test execution to report it elsewhere.
A JUnit 4 rule is a component that intercepts test method calls, and allows us to do something before a test method is run and after a test method has been run. JUnit 4 requires that rule fields are annotated with the @Rule annotation.
As the documentation of Rule
and TemporaryFolder
states, it takes care of creating a temporary directory before each test method of the respective class and deleting this temporary folder (and its content) after each test method.
You can write your own rules easily by implementing TestRule
or MethodRule
or extending from any of its implementing classes like ExternalResource
.
The same logic could be guaranteed with @Before
and @After
annotated initialization and cleanup methods. However, you would need to add the logic right into the test-class. If you need the logic in multiple test-classes, you either need to use inheritance, write some arbitrary utility-classes or externalize the behavior. Rules do exactly the latter one by allowing you to re-use this kind of initialization or cleanup-logic and furthermore reduce the code and remove unneeded code so that the focus is on the actual test rather than the configuration of some directories or servers.
This project for example declares two kind of servers (Jetty or Tomcat) you just have to annotate with @Rule
in order to use the server for integration or end-to-end tests.
If you want to initialize a rule only once for all test-methods, simply replace @Rule
with @ClassRule
and initialize the rule as public static
. This will initialize the class rule only once and will be reused for each test-method.
@ClassRule
public static JettyServerRule server = new JettyServerRule(new EmbeddedJetty());
@Test
public void myUnitTest() {
RestTemplate restTemplate = new RestTemplate();
String url = String.format("http://localhost:%s", server.getPort());
String response = restTemplate.getForObject(url, String.class);
assertEquals("Hello World", response);
}
The above sample would initialize a Jetty server only once and each test method of the test-class can reuse this server instead of starting and tearing down a new server for each method.
Multiple rules can even be combined with RuleChain
:
@Rule
public RuleChain chain= RuleChain.outerRule(new LoggingRule("outer rule")
.around(new LoggingRule("middle rule")
.around(new LoggingRule("inner rule");
In one of our integration-tests, which sent a couple of requests simultaneously to our JAX-RS service deployed via Restlet on Jetty we have the following rule-definitions:
public static SpringContextRule springContextRule;
public static RestletServerRule restletServer;
static {
springContextRule = new SpringContextRule(JaxRsRestletTestSpringConfig.class);
restletServer = new RestletServerRule(springContextRule.getSpringContext());
}
@ClassRule
public static RuleChain ruleChain = RuleChain.outerRule(restletServer).around(springContextRule);
// Tempus Fugit rules
@Rule
public ConcurrentRule concurrently = new ConcurrentRule();
@Rule
public RepeatingRule repeatedly = new RepeatingRule();
Which initializes a spring annotation context before the Restlet/Jetty server is started to enable Spring bean injection. Furthermore, the Tempus Fugit rules are utilized to execute test methods a couple of times concurrently in order to spot race-conditions and concurrency related problems earlier.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With