Skip to content

Exception testing

When writing unit tests, we usually start with testing the so-called "happy path". However, we should not forget to test the scenarios that throw exceptions.

try-catch

We can test exceptions using the try catch block and the fail assertion from JUnit 5, e.g .:

  @Test
  void shouldThrowException() {
    try {
      numbers.findFirstDigit("");
      fail("Exception was not thrown");
    } catch (final IllegalArgumentException exp) {
      assertEquals("Input cannot be empty", exp.getMessage());
    }

assertThrows

Junit 5 provides a set of tools to easily test exceptions in Java. By using dedicated assertions, you can catch an exception and validate its potential details. There are three overloads of such an assertion:

  • public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable)

  • public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable, String message)

  • public static<T extends Throwable> T assertThrows(Class <T> expectedType, Executable executable, Supplier <String> messageSupplier).

The first argument of this method is the expected type of the exception (or the type that this exception inherits from). The second argument is the functional interface, in which we should call the code snippet that we think should throw a specific exception. Optionally, we can further personalize the message in the event that such an exception does not occur.

The assertThrows assertion returns an instance of the exception thrown. Thanks to this, we can check, e.g. the exception message or the reason for throwing it (i.e. * cause *).

Examples

For the method below:

public static float divide(float a, float b) {
  if (b == 0) {
    throw new IllegalArgumentException("dividend can't be 0");
  }
  return a / b;
}

the test for the case where an exception is expected might look like this:

@Test
void shouldThrowIllegalArgumentException() {
  final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> divide(10, 0));

  assertThat(exception).hasMessage("dividend can't be 0")
      .hasNoCause();
}

AssertJ

The test for the case where an exception is expected might look like this:

  • assertThatExceptionOfType
  • assertThatThrownBy.

Furthermore, AssertJ provides specialized versions of the assertThatExceptionOfType method that expects a specific type of exception. It results from the name of the method, e.g .:

  • assertThatNullPointerException
  • assertThatIllegalArgumentException.

So we could test the divide method from the previous example in the following ways:

@Test
void shouldThrowIllegalArgumentException() {
  assertThatExceptionOfType(IllegalArgumentException.class)
      .isThrownBy(() -> divide(10, 0))
  .withMessage("dividend can't be 0")
  .withNoCause();
}
@Test
void shouldThrowIllegalArgumentException() {
  assertThatIllegalArgumentException()
      .isThrownBy(() -> divide(10, 0))
      .withMessage("dividend can't be 0")
      .withNoCause();
  }
@Test
void shouldThrowIllegalArgumentException() {
  assertThatThrownBy(() -> divide(10, 0))
      .isExactlyInstanceOf(IllegalArgumentException.class)
      .hasMessage("dividend can't be 0")
      .hasNoCause();
  }