JUnit: Custom ExpectedException Rules…Rule!
Join the DZone community and get the full member experience.
Join For FreeI’ve been using JUnit 4.7′s ExpectedException rule for some time now and I love it! IMHO, it combines the best of the old JUnit 3.x pattern for verifying expected exceptions and the expected attribute of JUnit 4′s @Test annotation, without their downsides. Although this is nothing new, I’ll explain them briefly:
The old JUnit 3.x pattern for verifying expected exceptions looks like this:
@Test public void shouldThrowNpeIfArgumentIsNull() { encoder.selectEncoding("Ewokeese"); try { encoder.encode(null); fail("Expecting NullPointerException"); } catch (NullPointerException expected) { assertEquals("The text to encode should not be null", expected.getMessage()); } }
The good thing about this pattern is that we can verify exactly where the exception we are expecting should be thrown. The downside is that is verbose and error-prone. It is easy to forget to call fail (line 6,) resulting in a test that never fails, even if the expected exception is never thrown!
JUnit 4 introduced the attribute expected in its @Test annotation, where we can specify the type of exception should be thrown by the code to test:
@Test(expected = NullPointerException.class) public void shouldThrowNpeIfArgumentIsNull() { encoder.selectEncoding("Ewokeese"); encoder.encode(null); }
IMHO, it made things worse. It definitely results in cleaner code, but the price to pay is that now we can’t check if the message of the expected exception is correct and we can’t specify the line of code that is supposed to throw an exception. If an NPE is thrown, which line actually did it?
Enter ExpectedException rule
JUnit 4.7 introduced rules, which are another extension mechanism for JUnit (you can read more about rules here.) Among those rules, my favorite is ExpectedException, because it allows us, with only a couple of lines of code, to verify both the type and the message of an expected exception *and* specify where the exception should be thrown (well, more or less.)
public class EncoderTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void shouldThrowNpeIfArgumentIsNull() { encoder.selectEncoding("Ewokeese"); thrown.expect(NullPointerException.class); thrown.expectMessage("The text to encode should not be null"); encoder.encode(null); } }
The sour smell of code duplication
So far so good. Something I noticed is that in my code I was checking for the same type of exceptions over and over, resulting in code duplication. For example, I had this code in too many places:
thrown.expect(AssertionError.class);
Yeah, I know, this is only one line of code, but duplication really bothers me. I wished I could write the following:
thrown.expectAssertionError("[Test] expecting:<'Leia'> but got <'Yoda'>");
Actually, it is possible. We just have to write our own ExpectedException rule. Subclassing org.junit.rules.ExpectedException is not possible, since its constructor is private. Copy/pasting ExpectedException‘s code is a terrible idea. There is a better way.
The solution
I implemented my own ExpectedException by simply wrapping the JUnit one, protecting myself from future changes in ExpectedException‘s implementation:
public class ExpectedException implements MethodRule { private final org.junit.rules.ExpectedException delegate = org.junit.rules.ExpectedException.none(); public static ExpectedException none() { return new ExpectedException(); } private ExpectedException() {} public Statement apply(Statement base, FrameworkMethod method, Object target) { return delegate.apply(base, method, target); } public void expectAssertionError(String message) { expect(AssertionError.class); expectMessage(message); } public void expectNullPointerException(String message) { expect(NullPointerException.class); expectMessage(message); } public void expect(Throwable error) { expect(error.getClass()); expectMessage(error.getMessage()); } public void expect(Class<? extends Throwable> type) { delegate.expect(type); } public void expectMessage(String message) { delegate.expectMessage(message); } }
Conclusion
The (relatively) new ExpectedException rule is a nice addition to JUnit. It brings the benefits of the previous approaches for verifying expected exceptions, without their downsides. As useful as it is, ExpectedException can also introduce code duplication if we are verifying the same type of exception many times in our test suite. As a workaround, creating our own ExpectedException that already knows about the type of exceptions we expect in our test suite can reduce code duplication and make test code a little more readable. Writing one is easy, we just need to implement the interface org.junit.rules.MethodRule and delegate the actual behavior to a org.junit.rules.ExpectedException, something that takes seconds to do in modern IDEs.
Feedback is always welcome :)
Opinions expressed by DZone contributors are their own.
Comments