Jake Goulding

Be careful when using JUnit's expected exceptions

For many people, JUnit is the grand-daddy of testing frameworks. Even though other testing frameworks came first, a lot of people got their start with JUnit.

People often start out testing with simple Boolean assertions, then move on substring matching, then maybe on to mocks and stubs. At some point, however, most people want to assert that their code throws a particular exception, and that’s where our story starts.

When JUnit 3 was the latest and greatest, you were supposed to catch the exception yourself and assert if no such exception was thrown. Here’s an example I tweaked from Lasse’s blog and the JUnit documentation for @Test.

1
2
3
4
5
6
7
8
9
@Test
public void test_for_npe_with_try_catch() {
    try {
        throw new NullPointerException();
        fail("should've thrown an exception!");
    } catch (NullPointerException expected) {
        // go team!
    }
}

With the newest versions of JUnit 4 (4.11 at the time of writing), there are two more options available to you: the @Test annotation and the ExpectedException rule.

1
2
3
4
@Test(expected = NullPointerException.class)
public void test_for_npe_with_annotation() {
    throw new NullPointerException();
}
1
2
3
4
5
6
7
8
@Rule
public ExpectedException thrown = ExpectedException.none();

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

Both of these forms offer a lot in the way of conciseness and readability, and I prefer to use them when I need to test this kind of thing. However, both forms can cause a test to pass when it shouldn’t when the code can throw the exception in multiple ways:

1
2
3
4
5
6
@Test(expected = NullPointerException.class)
public void test_for_npe_but_which_one() {
    CoolObject obj = new CoolObject(null);
    obj.doSomeSetupWork(42);  // What actually throws the exception
    obj.calculateTheAnswer(); // What we want to throw the exception
}

In languages that have lambdas or equivalents, this problem is easily avoided. For example, you can use expect and raise_error in RSpec:

1
2
3
4
5
it 'throws_a_npe' do
  obj = CoolObject.new(nil)
  obj.do_some_setup_work(42)
  expect { obj.calculate_the_answer }.to_raise(NoMethodError)
end

Alternate solutions

Until a version of Java is released with lambdas, I see no better solution than using try-catch blocks, the old JUnit 3 way. You could define an interface and then create anonymous classes in the test to have the desired level of granularity. This is a pretty bulky syntax, any variables you use in the object would need to be declared final, and then you have to explictly run the code!

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test_for_one_of_two_npe_bulky_syntax() {
    final CoolObject obj = new CoolObject(null);
    obj.doSomeSetupWork(42);

    new GonnaThrowException(NullPointerException.class) {
        public void test() {
            obj.calculateTheAnswer();
        }
    }.run();
}

If you can rephrase the problem slightly, you might be able to use the fact that ExpectedException can assert on the exception message to restrict your test. If you know that only your error can include a certain string, then checking for that string could prevent tests from passing when they shouldn’t.

Another solution would be to modify your code or tests so that you don’t have to deal with the problem in the first place. If you can move the setup code into a @Before block, then the exception wouldn’t be caught by the test. If you can change your code so it cannot throw the exception multiple ways, or if it throws different exceptions, then that would also allow you to sidestep the problem.

Update 2012-09-27

David Bradley points out that if you configure the JUnit rule right before the expected exception, you can reduce the possibility of error. Unfortunately, exceptions thrown after the desired line will still cause the test to pass incorrectly. This may not be a problem in practice, as you are unlikely to continue the test after an exception should be thrown, and most Java tests do not have a teardown phase.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void test_for_npe_with_rule_at_last_moment() {
    CoolObject obj = new CoolObject(null);
    obj.doSomeSetupWork(42);

    thrown.expect(NullPointerException.class);
    obj.calculateTheAnswer();

    // Any exceptions here will still cause the test to pass incorrectly
}