About Rafal Borowiec

Rafal is an IT specialist with about 8 years of commercial experience, specializing in software testing and quality assurance, software development, project management and team leadership.

3 ways of handling exceptions in JUnit. Which one to choose?

In JUnit there are 3 popular ways of handling exceptions in your test code:

  • try-catch idiom
  • With JUnit rule
  • With annotation

Which one should we use and when?

try-catch idiom

This idiom is one of the most popular one, because it was used already in JUnit 3.

@Test
    public void throwsExceptionWhenNegativeNumbersAreGiven() {
        try {
            calculator.add("-1,-2,3");
            fail("Should throw an exception if one or more of given numbers are negative");
        } catch (Exception e) {
            assertThat(e)
                    .isInstanceOf(IllegalArgumentException.class)
                    .hasMessage("negatives not allowed: [-1, -2]");
        }
    }

The above approach is a common pattern. The test will fail when no exception is thrown and the exception itself is verified in a catch clause (in the above example I used the FEST Fluent Assertions) and although it is perfectly fine I prefer the approach with
ExpectedException rule.

With JUnit rule

The same example can be created using
ExceptedException rule. The rule must be a public field marked with @Rule annotation. Please note that the “thrown” rule may be reused in many tests.

@Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void throwsExceptionWhenNegativeNumbersAreGiven() {
        // arrange
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage(equalTo("negatives not allowed: [-1, -2]"));
        // act
        calculator.add("-1,-2,3");
    }

In general, I find the above code more readable hence I use this approach in my projects.

When the exception isn’t thrown you will get the following message: java.lang.AssertionError: Expected test to throw (an instance of java.lang.IllegalArgumentException and exception with message “negatives not allowed: [-1, -2]“). Pretty nice.

But not all exceptions I check with the above approach. Sometimes I need to check only the type of the exception thrown and then I use @Test annotation.

With annotation

@Test (expected = IllegalArgumentException.class)
    public void throwsExceptionWhenNegativeNumbersAreGiven() {
        // act
        calculator.add("-1,-2,3");
    }

When the exception wasn’t thrown you will get the following message: java.lang.AssertionError: Expected exception: java.lang.IllegalArgumentException

With this approach you need to be careful though. Sometimes it is tempting to expect general Exception, RuntimeException or even a Throwable. And this is considered as a bad practice, because your code may throw exception in other place than you actually expected and your test will still pass!

To sum up, in my code I use two approaches: with JUnit rule and with annotation. The advantages are:

  • Error messages when the code does not throw an exception are automagically handled
  • The readability is improved
  • There is less code to be created

And what is your preference?

Edit – the 4th way

I have heard of the 4th way of handling the exception (one of my colleagues suggested it after reading this post) – use custom annotation.

Actually the solution seems nice at first glance, but it requires your own JUnit runner hence it has disadvantage: you cannot use this annotation with e.g. Mockito runner.

As a coding practice I have created such an annotation, so maybe someone finds it useful

The usage

@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 above test will fail with a message: java.lang.Exception: Unexpected exception message, expected

but was

An annotation

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ExpectsException {
    Class type();

    String message() default "";
}

A runner with some copy & paste code

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<"
                            + expected.getName() + "> but was <"
                            + e.getClass().getName() + ">";
                    throw new Exception(message, e);
                }

                if (isNotNull(expectedMessage) && !expectedMessage.equals(e.getMessage())) {
                    String message = "Unexpected exception message, expected<"
                            + expectedMessage + "> but was<"
                            + e.getMessage() + ">";
                    throw new Exception(message, e);
                }
            }
            if (complete) {
                throw new AssertionError("Expected exception: "
                        + expected.getName());
            }
        }

        private boolean isNotNull(String s) {
            return s != null && !s.isEmpty();
        }
    }

}

 

Related Whitepaper:

Bulletproof Java Code: A Practical Strategy for Developing Functional, Reliable, and Secure Java Code

Use Java? If you do, you know that Java software can be used to drive application logic of Web services or Web applications. Perhaps you use it for desktop applications? Or, embedded devices? Whatever your use of Java code, functional errors are the enemy!

To combat this enemy, your team might already perform functional testing. Even so, you're taking significant risks if you have not yet implemented a comprehensive team-wide quality management strategy. Such a strategy alleviates reliability, security, and performance problems to ensure that your code is free of functionality errors.Read this article to learn about this simple four-step strategy that is proven to make Java code more reliable, more secure, and easier to maintain.

Get it Now!  

2 Responses to "3 ways of handling exceptions in JUnit. Which one to choose?"

  1. Blazej L. says:

    I would recommend you the 5th way – the catch-exception library. It’s biggest advantage is that it allows you to catch exception only on a concrete method invocation. So, if you want to assert that NullPointerException is being thrown on a tested method, and not on the one of “arange” methods, catch-exception is the only way to do that. You can read more on that on the project’s page: https://code.google.com/p/catch-exception/

  2. IBYoung says:

    With annotation way

Leave a Reply


− one = 0



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

15,153 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books