JUnit Exception Handling Example
1. Introduction
There are popular ways to test exception in JUnit. A developer can use the traditional try-catch statement, the @Rule
or the annotation based. In this post, I’ll be discussing these 3 simple and easy to implement ways to make your test case and functional scenarios bulletproof of incoming exceptions.
2. Source(s)
In this example, we use the traditional way catching an exception. We use the try-catch clause to catch and throw an assertion condition that returns the test case result.
JUnitTryCatchExample.java
package com.areyes1.junit.exceptions; import static org.junit.Assert.*; import org.junit.Test; public class JUnitTryCatchExample { double value = 0.0d; /** * We are catching the exception using the traditional try-catch clause and return * an assertion. */ @Test public void testJUnitTryCatch() { try { if(value < 0.1d) { fail("Value given is not as expected"); } } catch (Exception e) { assertTrue((value != 0.0d)); } } }
Let’s go through the code.
- We introduce a double variable equating to 0.0. This will be our basis of the test case
- We created a method and tag it as
@Test
as this is a test case. - We introduce a try catch and wrap the process of our test case. In our example, we want to check if the value is less the 0.1. If it does, it manually fail and throws the exception
- The exception then evaluate an assertion and throws the overall result of the test case.
This is a basic example of how can we use the traditional try-catch clause on our test case, now let’s look at on the annotation based approach.
JUnitRuleAnnotation.java
package com.areyes1.junit.exceptions; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; public class JUnitRuleAnnotation { // We introduce this to create an expected exception object @Rule public ExpectedException expectedThrown = ExpectedException.none(); /** * The test is expecting throw the expected exception (which in this case, the NumberFormatException) */ @Test public void testExpectedRuleException() { expectedThrown.expect(NumberFormatException.class); String numberStr = "abc"; Integer.valueOf(numberStr); } }
Unlike the try-catch statement clause usage, we use the @Rule
annotation to create an expected exception object. This is used to create an expected object exception for the test case to make sure that it throws the expected exception.
Let’s go deep into our code.
- We introduce a
@Rule
annotation. This is a specialized annotation to tag an object that we are imposing a rule for our test case. - We introduce the test method using
@Test
- We use the expectedThrown method and pass over the expected exception. This will then be used on the evaluation of the test case method.
- Finally we introduce the actual code that we wanted to test. In this case, we want to test the integer boxing of a string. We are expecting that it throws a NumberFormatException.
The @Rule
annotation allows us to specify a specific Exception class that the test case expects. This is a very powerful Junit feature as it will allow developers to fail proof their methods.
Aside from the @Rule
, we can actually pass an expected exception class on the @Test annotation. See example below.
JUnitAnnotationExample.java
package com.areyes1.junit.exceptions; import org.junit.Test; public class JUnitAnnotationExample { /** * This means that the method is expecting a number format exception. */ @Test(expected=NumberFormatException.class) public void testAnnotationExample() { String numberStr = "abc"; Integer.valueOf(numberStr); } }
My personal favourite, using annotations only. The @Test
annotation accepts an expected and message parameter, to tag the test method that it will only return the expected exception and the message it will give you if ever it didn’t.
Let’s go over the method
- We create a typical annotation based test case using
@Test
- Instead of creating an
@Rule
, we use the expected attribute of the@Test
to pass the expected exception. - We test our code with it.
This is somehow a more cleaner and straightforward approach. Instead of creating an @Rule
explicitly, we use the expected attribute for our test case.
3. Add-on: Custom Annotations
It is possible to create a custom annotation class to be used for test cases. This will allow developers to fully customise the behaviour of the test case once an assertion is evaluated and an exception is thrown.
StringCalculatorTest.java
@RunWith(ExpectsExceptionRunner.class) public class StringCalculatorTest { @Test @ExpectsException(type = IllegalArgumentException.class, message = "negatives not allowed: [-1]") public void throwsExceptionWhenNegativeNumbersAreGiven() throws Exception { // act calculator.add("-1,-2,3"); } }
the annotation
ExpectsException.java
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface ExpectsException { Class type(); String message() default ""; }
runner class (copy pasted code)
ExpectsExceptionRunner.java
public class ExpectsExceptionRunner extends BlockJUnit4ClassRunner { public ExpectsExceptionRunner(Class klass) throws InitializationError { super(klass); } @Override protected Statement possiblyExpectingExceptions(FrameworkMethod method, Object test, Statement next) { ExpectsException annotation = method.getAnnotation(ExpectsException.class); if (annotation == null) { return next; } return new ExpectExceptionWithMessage(next, annotation.type(), annotation.message()); } class ExpectExceptionWithMessage extends Statement { private final Statement next; private final Class expected; private final String expectedMessage; public ExpectExceptionWithMessage(Statement next, Class expected, String expectedMessage) { this.next = next; this.expected = expected; this.expectedMessage = expectedMessage; } @Override public void evaluate() throws Exception { boolean complete = false; try { next.evaluate(); complete = true; } catch (AssumptionViolatedException e) { throw e; } catch (Throwable e) { if (!expected.isAssignableFrom(e.getClass())) { String message = "Unexpected exception, expected but was "; throw new Exception(message, e); } if (isNotNull(expectedMessage) && !expectedMessage.equals(e.getMessage())) { String message = "Unexpected exception message, expected but was"; throw new Exception(message, e); } } if (complete) { throw new AssertionError("Expected exception: " + expected.getName()); } } private boolean isNotNull(String s) { return s != null && !s.isEmpty(); } } }
4. Download the Eclipse project of this tutorial:
This was an example of testing exceptions with JUnit.
You can download the full source code of this example here: junit-exception-example