Explain Codes LogoExplain Codes Logo

Junit 5: How to assert an exception is thrown?

java
exception-handling
junit
lambda
Nikita BarsukovbyNikita Barsukov·Aug 7, 2024
TLDR

Use JUnit 5's assertThrows for basic exception assertion:

import static org.junit.jupiter.api.Assertions.assertThrows; @Test void exceptionThrowingTest() { assertThrows( IllegalArgumentException.class, () -> { throw new IllegalArgumentException("Making a fuss over invalid arguments"); } ); }

The code snippet ensures the expected IllegalArgumentException is thrown.

Expand the assertion to verify exception details, such as the message content:

import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @Test void checkingExceptionDetailsTest() { Exception thrownException = assertThrows( IllegalArgumentException.class, () -> { throw new IllegalArgumentException("Unacceptably invalid arguments!"); } ); assertTrue(thrownException.getMessage().contains("Unacceptably invalid arguments!")); }

This allows us to validate precise exceptions with desired attributes.

Upgrade your exception tests

For stricter type checks in your exceptions, assertThrowsExactly comes handy. It is available for users of JUnit 5.8.0+.

import static org.junit.jupiter.api.Assertions.assertThrowsExactly; @Test void preciseExceptionTypeCheckTest() { assertThrowsExactly( IllegalArgumentException.class, () -> { throw new IllegalArgumentException("Did someone pass a bad argument, or is it just me?"); } ); }

Rest assured, false positives from exception subtypes are now out of the question!

Shedding light on the assert functionality

Functional interfaces and lambdas

JUnit 5 enables us to make use of Java 8's lambda expressions within assertThrows and assertThrowsExactly. The functional interface Executable encapsulates the suspect code. In other words, it does the dirty work!

When does the test fail?

A test can turn a frown upside down (i.e., fail) under these conditions:

  • No drama, no exception to make an appearance.
  • An exception crash the party, but not the one we were expecting.

assertThrowsExactly is pickier. Don't expect it to pass just because an exception's subtype decided to show up!

Checking the "gossip" (exception messages)

More often than not, we're interested in the message that the exception carries — the gossip. Check its content within assertThrows:

Exception exception = assertThrows( IllegalArgumentException.class, () -> { throw new IllegalArgumentException("Expected gossip!"); } ); assertTrue(exception.getMessage().contains("Expected gossip!"));

Deep dive into exception handling in tests

Handling multiple exceptions

Keep calm and use assertThrows multiple times within the same test method when the code under test is capable of throwing multiple exceptions. Your tests will still rock!

Labelling your tests

Want your test reports to make sense at a glance? Use @DisplayName with assertThrows:

@Test @DisplayName("Should throw IllegalArgumentException when bad things happen") void namedExceptionTest() { assertThrows(IllegalArgumentException.class, () -> { /* Place where bad things happen */ }); }

Alternatives for the adventurous

Not satisfied with assertThrows? Try AssertJ for a fluent exception assertion API:

import static org.assertj.core.api.Assertions.assertThatThrownBy; @Test void exceptionTestingWithAssertJ() { assertThatThrownBy(() -> { /* Where the "bad guy" pops in */ }) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("The bad news"); }

References