Filed under: Java, Programming, TDD, — Tags: @Rule, Expected exception, JUnit, checked exception — Thomas Sundberg — 2015-11-20
Sometimes you want to verify that an exception is thrown in your code.
Let me show you three different ways to verify that the expected exception has been thrown.
One solution that always works would be the one below:
@Test public void should_throw_runtime_exception_naive() { try { throwExampleException(); fail("Expected a RuntimeException"); } catch (RuntimeException e) { assertThat(e.getMessage(), is("Oops!")); } }
You execute the action the test should check and catches a specific exception. And then check that the expected message actually is the message you see.
What is the problem then? The only issue I can think of is that it is unnecessary verbose. I am not comfortable with test code that isn't straight through. In this case, there is a catch clause. In other cases there may be loops or even worse, conditions.
So to me, this works but it isn't pretty.
An approach that I often use is to annotate the test with the expected exception. It can look like this:
@Test(expected = RuntimeException.class) public void should_throw_runtime_exception() { throwExampleException(); }
This is smaller. And smaller is good. But it is also a bit blunt. You will get feedback from the test when the expected exception isn't thrown. But you will not get feedback if the message is something different from what you expected.
Given these two options, I usually prefer the last one. Even when I am not able to verify the message. The compactness of the code is appealing.
If I were able to use an annotation like @Test(expected = RuntimeException.class, messgae = "Oops!")
then that would have been a very nice solution. But JUnit doesn't support that.
There is a third way to do this. That is to use a JUnit @Rule
annotation with
ExpectedException
. Let me show you how.
You need to define a JUnit rule. And use it. It can be done like this:
@Rule public ExpectedException thrown = ExpectedException.none(); @Test public void should_also_throw_runtime_exception() { thrown.expect(RuntimeException.class); thrown.expectMessage("Oops!"); throwExampleException(); }
This rule says that I don't expect any exceptions to be thrown from my method. This means that the behaviour is consistent with the default behaviour of JUnit.
I can, however, define that an exception should be thrown in a test and define the class for the exception.This is
what I do in should_also_throw_runtime_exception()
. And all of a sudden, I am not only able to assert
the class for the thrown exception, I am also able to assert the message that was thrown. And I am able to do it in
a test method where I don't have any blocks.
I think it would have been nice to extend the test annotation to handle both exception and its message, but using
this rule construction is almost just as good as an extended annotation. I am able to verify more things, using the
field thrown
in this example, so I guess it is a resonable limitation.
I don't like blog posts that hides stuff from me. Especially imports in example code. It makes the examples magic and I am not impressed by magic. The complete source code I used for this blog is therefore included below.
Enjoy!
package se.thinkcode; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.junit.Assert.fail; public class CheckExceptionsTest { @Test public void should_throw_runtime_exception_naive() { try { throwExampleException(); fail("Expected a RuntimeException"); } catch (RuntimeException e) { assertThat(e.getMessage(), is("Oops!")); } } @Test(expected = RuntimeException.class) public void should_throw_runtime_exception() { throwExampleException(); } @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void should_also_throw_runtime_exception() { thrown.expect(RuntimeException.class); thrown.expectMessage("Oops!"); throwExampleException(); } private void throwExampleException() { throw new RuntimeException("Oops!"); } }
I would like to thank Malin Ekholm for proof reading.