What does @Rule do?

2020-07-11 09:11发布

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.

标签: java junit rule
1条回答
forever°为你锁心
2楼-- · 2020-07-11 10:02

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.

查看更多
登录 后发表回答