Clean Test Classes Using JUnit's Rules

A couple of days ago I discovered the beauty of JUnit’s TestRules while searching for an easy way to set a time-out on all tests in a testcase. JUnit has a built-in rule for this called Timeout. You can set this rule for every test in your class by setting the timeout in a field like this:

Setting a Timeout RuleView the Javadoc
1
2
3
4
5
6
7
8
9
10
public class MyTest {
  
  @Rule
  public MethodRule globalTimeout= new Timeout(20);
  
  @Test
  public void someTest() {
      ...
  }
  

Another gem is the ExpectedException rule, which allows you to inspect a thrown exception in several ways.

Inspecting excptionsView the Javadoc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static class HasExpectedException {
  @Rule
  public ExpectedException thrown= ExpectedException.none();

  @Test
  public void throwsNothing() {
  // no exception expected, none thrown: passes.
  }

  @Test
    public void throwsNullPointerException() {
      thrown.expect(NullPointerException.class);
      throw new NullPointerException();
  }

  @Test
  public void throwsNullPointerExceptionWithMessage() {
      thrown.expect(NullPointerException.class);
      thrown.expectMessage("happened?");
      thrown.expectMessage(startsWith("What"));
      throw new NullPointerException("What happened?");
  }
 }

The great thing is, it’s super easy to extend one of these rules.

In Crawljax, another project I’m currently working on, I wanted a Jetty server to start before I the tests run, and to shut it down afterwards. I could do this using a @BeforeClass method and then clean it up in the @AfterClass method but that doesn’t make it reusable in other classes. To make it reusable I could put it in an abstract class that just has the setup and teardown methods and inherit that class in the classes where I need the server. However, that can lead to weird class hierarchies that don’t make any sense. Again, JUnit’s rules come to the rescue. There’s the ExternalResource that allows you to setup resources before tests, and tear them down afterwards. I inherited this class to provide my Jetty server.

Rule to start a Jetty ServerView on GitHub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class RunWithWebServer extends ExternalResource {

  private final Resource resource;

  private int port;
  private URL demoSite;
  private Server server;
  private boolean started;

  /**
  * @param classPathResource
  *            The name of the resource. This resource must be on the test or regular classpath.
  */
  public RunWithWebServer(String classPathResource) {
      resource = Resource.newClassPathResource(classPathResource);
  }

  @Override
  protected void before() throws Throwable {
      server = new Server(0);
      ResourceHandler handler = new ResourceHandler();
      handler.setBaseResource(resource);
      server.setHandler(handler);
      server.start();
      this.port = server.getConnectors()[0].getLocalPort();
      this.demoSite = new URL("http", "localhost", port, "/");
      this.started = true;
  }

  @Override
  protected void after() {
      try {
          if (server != null) {
              server.stop();
          }
      } catch (Exception e) {
          throw new RuntimeException("Could not stop the server", e);
      }
  }

  // Some getters for the fields
}

The class starts the Jetty server using the given resource as the web folder. Tests can then use this rule to obtain a real URL to test with:

Using the webserver.Usage example
1
2
3
4
5
6
7
8
9
10
11
12
public void SomeWebTest {

  @ClassRule
  public static final RunWithWebServer SERVER = new RunWithWebServer("/site/crawler");
  
  @Test
  public void test() {
      URL url = SERVER.getSiteUrl();
      testStuffUsingThe(url); 
  }
  
}

There are more built-in rules in JUnit like:

  • The TemporaryFolder That creates a temporary folder for you,
  • the Verifier that can verify some invariant after each test method,
  • and more! Checkout the JUnit wiki to learn more of them.

Have fun!


Comments