Catching Exceptions in Unit Tests

Know when to catch them and when to just let them go.

Catching Exceptions in Unit Tests

This was quite serendipitous as I was just about to start looking for a blog topic that started with the letter "C". It just fell on my lap today while reviewing someone else's pull request, particularly their unit tests.

This is specifically about JUnit, but it may also apply to your unit-testing framework and language of choice.

I noticed that they were catching exceptions and then printing them out, like below:

@Test
public void someTest() {
    try {
    ... test code here ...
    } catch (NullPointerException e) {
        System.out.println("Caught NullPointerException: " + e.getMessage()); 
    }
}

For someone new to unit-testing, this looks harmless. On the contrary, this is actually dangerous. If the code does throw NullPointerException, then, sure, you'll see something printed out, but your build would still pass. And when the build passes, no one usually looks in the logs. You would miss what is possibly a bug in your code.

You want your build to fail if you do get an unexpected NullPointerException (or any exception for that matter). So, what do you do in this case? Just let it go. Let your test method throw it:

@Test
public void someTest() {
    ... test code here ...
}

Or if anything in the test code could throw an unwanted checked exception, say, IOException, then just add it to the test method's signature, e.g.:

@Test
public void someTest() throws IOException {
    ... test code here ...
}

What if you do expect an exception?

There are several ways of doing this.

One option is to catch it. Yes, this is when you could catch it. You could then optionally assert whatever you want to assert in the exception, e.g.:

@Test
public void someTest() {
    try {
        ... test code here ...
        fail("Expected MyException!"); // don't forget this!
    catch (MyException e) {
        // sample optional asserts based on what you need;
        // if you don't need any, then you can leave this block empty.
        assertEquals(101, e.getCode());
        assertEquals(SomeOtherException.class, e.getCause().getClass());
    }
}

You need that fail() statement there, as otherwise, if the test code does not throw MyException when you expect it to, it would incorrectly pass.

If you do not need to assert anything, you could also just do the following if you're using JUnit 4:

@Test(expected=MyException.class)
public void someTest() {
    ... test code here ...
}

JUnit 5 introduces a new way of handling exceptions. The following test expects MyException to be thrown as in the previous examples.

import static org.junit.jupiter.api.Assertions.assertThrows;

@Test
public void someTest() {
    MyException e = assertThrows(
           MyException.class,
           () -> ... test code here ...,
           "Optional third parameter for a custom failure message"
    );

    // the same optional asserts that we had in an earlier example 
    assertEquals(101, e.getCode());
    assertEquals(SomeOtherException.class, e.getCause().getClass());
}

If you do not need any asserts against the Exception object, you can collapse the above into:

import static org.junit.jupiter.api.Assertions.assertThrows;

@Test
public void someTest() {
    assertThrows(
           MyException.class,
           () -> ... test code here ...,
           "Optional third parameter for a custom failure message"
    );
}

That is, you can simply ignore the Exception object returned by assertThrows(). It would still fail if MyException is not thrown by the test code.

Lastly, JUnit4 actually also has an ExpectedException class, but it was never intuitive to me. I've used it once or twice, but it's one of those things that I'd have to look up again in order to use. So I decided against covering it here. Does anyone out there like it? I'd love to hear your thoughts on it.