Saturday, November 11, 2006

Throwing exceptions from setUp in JUnit tests

JUnit makes use of the template method pattern when running tests, i.e. if we have a test like below:

public class MyTest extends TestCase {

  @Override protected void setUp() throws Exception {
    super.setUp();
    System.out.println("MyTest.setUp");
  }

  public void testInvocationSequence () throws Exception {
    System.out.println("MyTest.test");
  }

  @Override protected void tearDown() throws Exception {
    System.out.println("MyTest.tearDown");
    super.tearDown();
  }
    }


On the console we see:

MyTest.setUp
MyTest.testInvocationSequence
MyTest.tearDown

confirming that the sequence of test invocation is the setUp method first, then the test itself and finally the tearDown method.

Let's make this slightly more interesting. What happens when the test method throws an exception?

  public void testInvocationSequence () throws Exception {
    System.out.println("MyTest.testInvocationSequence");
    throw new Exception("testing the JUnit test execution sequence");
  }


On the console we still see:
MyTest.setUp
MyTest.testInvocationSequence
MyTest.tearDown

java.lang.Exception: testing the JUnit test invocation sequence!

But this time the test fails. The important thing to note here is that tearDown is called despite the fact that test execution ended abruptly. This makes it safe to initialize expensive resources in the setUp method and free them in the tearDown method. For instance, one might open a database connection in the setUp method and close it in the tearDown method.


However, what happens if the setUp method itself fails?

  @Override protected void setUp() throws Exception {
    super.setUp();
    System.out.println("MyTest.setUp");
    throw new Exception("testing the JUnit test execution sequence");
  }

The output on the console is interesting:

MyTest.setUp
java.lang.Exception: testing the JUnit test invocation sequence!

The test method (predictably) and the tearDown (strangely?) did not get invoked! If this failure in the setUp method happens after the expensive resource is initialized we will end up leaking resources. A quick look at the runBare method in the TestCase class helps clarify how things work.

  public void runBare() throws Throwable {

    Throwable exception = null;
    setUp();
    try {
      runTest();
    } catch (Throwable running) {
      exception = running;
  } finally {
    try {
      tearDown();
    } catch (Throwable tearingDown) {
      if (exception == null) exception = tearingDown;
    }
  }
  if (exception != null) throw exception;
}

The important thing to note here is that the setUp method is invoked outside the try block. If the setUp method fails, neither the test, nor the tearDown are going to be invoked.

How does one reliably initialize expensive resources and free them when the test ends? Some JUnit enthusiasts may argue that tests requiring expensive resources should not be part of a test suite and should be written using another tool. Sometimes, this is just not an option. For such cases, one might override the runBare() method itself as follows:

@Override public void runBare() throws Throwable {
  try {
    // Initialize expensive resource
    super.runBare();
  } finally {
    // Free expensive resource
  }
}