Home » Java » Core Java » Testing for expected exceptions in JUnit

About Shaun Abram

Testing for expected exceptions in JUnit

Unit tests are used to verify that a piece of code operates as the developer expects it to. Sometimes, that means checking that the code throws expected exceptions too. JUnit is the standard for unit testing in Java and provides several mechanisms for verifying exceptions were thrown. This article explores the options and their relative merits.
 
Take the following simple code snippet as an example. As well as writing tests to ensure the canVote() method returns true or false, you should also writes tests to verify that it throws an IllegalArgumentException when expected.
 
 

  public class Student {
        public boolean canVote(int age) {
            if (i<=0) throw new IllegalArgumentException("age should be +ve");
            if (i<18) return false;
            else return true;
        }
    }

(Guava preconditions might be more suitable for these argument checks, but the example is still valid).

There are 3 common ways to check that exceptions are thrown, each with their own advantages and disadvantages.

1) @Test(expected…)

The @Test annotation has an optional parameter, “expected”, that allows you to specify a subclass of Throwable. If we wanted to verify that canVote()() method above throws the correct exception, we would write:

    @Test(expected = IllegalArgumentException.class)
    public void canVote_throws_IllegalArgumentException_for_zero_age() {
        Student student = new Student();
        student.canVote(0);
    }

Simple and concise, if a little imprecise since it tests that the exception will be thrown somewhere in the method, rather than on a specific line.

2) ExpectedException

To use JUnit’s ExpectedException, first you need to declare the ExpectedException:

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

Then you can either use the simpler approach of specifying the expected exception only:

@Test
    public void canVote_throws_IllegalArgumentException_for_zero_age() {
        Student student = new Student();
        thrown.expect(NullPointerException.class);
        student.canVote(0);
    }

Or can also specify the expected exception message too:

@Test
    public void canVote_throws_IllegalArgumentException_for_zero_age() {
        Student student = new Student();
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("age should be +ve");
        student.canVote(0);
    }

As well as being able to specify the expected exception message, this ExpectedException approach has the advantage of allowing you to be more precise about where the exception is expected to be thrown. In the above example, an unexpected IllegalArgumentException thrown in the constructor would cause the test to fail since we expected it to be thrown in the canVote() method.

On a side note, it would be nice if there wasn’t a need for the declaration:

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

It just seems like unnecessary noise. It would be nice to just be able to do

 expect(RuntimeException.class)

or

 expect(RuntimeException.class, “Expected exception message”)

or at least be able to pass the exception and message in an single call to ExpectedException:

 thrown.expect(IllegalArgumentException.class, “age should be +ve”);

3) Try/catch with assert/fail

Prior to JUnit4, the way to check for exceptions was to use try/catch blocks.

@Test
    public void canVote_throws_IllegalArgumentException_for_zero_age() {
        Student student = new Student();
        try {
            student.canVote(0);
        } catch (IllegalArgumentException ex) {
            assertThat(ex.getMessage(), containsString("age should be +ve"));
        }
        fail("expected IllegalArgumentException for non +ve age");
    }

Although an older approach, it is still perfectly valid. The main downside is that it is easy to forget to put the fail() after the catch, resulting in false positives if the expected exception is not thrown. I have certainly made this mistake in the past!

In conclusion, there are three main approaches for testing expected exceptions get thrown, each with its own advantages and disadvantages. For me personally, I usually lean towards the ExpectedException approach due to its precision and ability to test the exception message.

 

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!

1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design

and many more ....

 

2 comments

  1. Hi Shaun,

    In the third item, won’t unit test always fail because it will always go to that line no matter what?

    Regards,
    Francis

  2. Hi Francis,
    Yes, you are right, the 3rd test will always fail. There should be a fail() after the line where the exception is expected (student.canVote(0)), before the catch. This was a mistake I fixed in the original post, but I’m not sure how to fix on javacodegeeks.

    See the fixed version at shaunabram.com/testing-for-expected-exceptions-in-junit

    Thanks,

    Shaun

Leave a Reply

Your email address will not be published. Required fields are marked *

*


Want to take your Java Skills to the next level?
Grab our programming books for FREE!
  • Save time by leveraging our field-tested solutions to common problems.
  • The books cover a wide range of topics, from JPA and JUnit, to JMeter and Android.
  • Each book comes as a standalone guide (with source code provided), so that you use it as reference.
Last Step ...

Where should we send the free eBooks?

Good Work!
To download the books, please verify your email address by following the instructions found on the email we just sent you.