To make an assertion that an exception was thrown with JUnit, it’s fairly common to use the try/fail/catch idiom or
expected element of the
@Test annotation. Despite being more concise than the former,
there is an argument that using
expected doesn’t support all the cases you may want to test. The example being
to perform additional testing after the exception or testing against the actual exception message.
JUnit 4.7 introduces the next progression, a
@Rule that offers the best of both worlds. This articles weighs up the pros and cons of each approach and takes a closer look at the syntax of each.
The try/fail/catch Idiom
The typical pattern is to catch an exception or fail explicitly if it was never thrown.
which would highlight a failure in the following way.
java.lang.AssertionError: expected an exception at org.junit.Assert.fail(Assert.java:91) at bad.roboot.example.ExceptionTest.example1(ExceptionTest.java:20) ...
The idiom has potential advantages in that it offers the opportunity to assert against the actual exception as well as performing additional work after the expectation. Aside from the noise, the major drawback however is that its very easy to forget to include the
fail call. If genuinely doing test first, where we always run the test red, this wouldn’t be a problem but all too often things slip through the net. In practice, I’ve seen far too many examples with a missing
fail giving false positives.
@Test (expected = Exception.class)
expected element, we can rewrite the test as follows.
which will result in the following failure.
java.lang.AssertionError: Expected exception: bad.robot.example.NotFoundException
Much more concise, we’ve done away with all the noise at the cost of not being able to assert against the exception
message. We’ve also lost the ability to make more assertions after
find. However, you might decide that smaller focused tests are in fact a good thing. Using this syntax, we’re lead into writing a test focused on just one thing; that an exception is thrown when we call
The test feedback has also become clearer.
Using an instance of
ExpectedException, we can define a JUnit rule
that allows us to setup expectations that are checked after the test concludes. It has a similar feel to
setting up expectations in mocking frameworks like JMock.
Which would show the failure below.
java.lang.AssertionError: Expected test to throw (exception with message a string containing "exception message" and an instance of bad.robot.example.NotFoundException) at org.junit.rules.ExpectedException$ExpectedExceptionStatement.evaluate(ExpectedException.java:118) ...
The rule allows us to assert the exception is thrown and make assertions against the message. We still can’t make
additional assertions after the
find method call, but this may not be a bad thing.
Beware combining Rules with @RunWith
Beware though that if you combine the rule with certain
you may get a false positive. Specifically, if you were to run with a class that extends
JUnit4ClassRunner in the
above example, the test would no longer fail. You’d get a false positive.
For example, if you’re using a version of JMock prior to 2.6.0 and use
@RunWith(JMock.class) you’ll encounter this. Older versions of the
JUnit4ClassRunner ignores rules. The newer
BlockJUnit4ClassRunner supports rules and JMock post 2.6.0 extends this class in
The new rule offers a balance between concise syntax and function. In practice though if you’re not interested in asserting against the exception’s message, the
expected element offers the most straight forward syntax. In the next article Exception Handling as a System Wide Concern, I describe a general exception handling approach which negates the need to assert against exception messages.
ExpectedException rule comes with its own baggage. The declarative nature of the rule means magic just happens and so there is a new kind of “noise” to cope with in the test. You may or may not be comfortable with this.
I’d love to hear which approach you prefer, so feel free to post a comment below.
- Pragmatic Unit Testing in Java with Junit (Pragmatic Programmers), Andy Hunt, Dave Thomas
- Growing Object-Oriented Software, Guided by Tests, Steve Freeman, Nat Pryce
- Test Driven: TDD and Acceptance TDD for Java Developers, Lasse Koskela