@Test(expected = Exception.class) or Assertions.assertThrows(...) or assertThatThrownBy(..) which one is recommended?

Viewed 21679

I am using JUnit 4.12

<dependency>
    <groupId>pl.pragmatists</groupId>
    <artifactId>JUnitParams</artifactId>
    <version>1.0.5</version>
    <scope>test</scope>
</dependency>

I wanted to know which one is most recommended to use @Test(expected = Exception.class) or Assertions.assertThrows(...)

2 Answers

With JUnit 4.12 there are several ways to test code for expected exceptions.

try-catch

We can simply use Java's try-catch.

@Test
public void testInvalidData() {
    prepareTestData();

    try {
        userService.fetchUser(1234);
        Assert.fail("IllegalArgumentException not thrown");
    } catch (IllegalArgumentException expected) {
    }
}

Whenever we use this approach, we have to make sure to call Assert.fail(...) in case the expected exception was not thrown.

Annotation Attribute

As you already mentioned, the @Test has an attribute to declare the expected exception.

@Test(expected = IllegalArgumentException.class)
public void testInvalidData() {
    prepareTestData();

    // should throw IllegalArgumentException
    userService.fetchUser(1234);
}

The test is green if the test method throws the exception. The test is red if the test method throws no exception or a different exception.

This has one big disadvantage: We can't figure out which instruction throws the IllegalArgumentException. If prepareTestData(); throws the exception, the test is still green.

Rule ExpectedException

JUnit 4 contains the built-in rule ExpectedException. (please remember that JUnit 5 uses extensions instead of rules)

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

@Test
public void testInvalidData() {
    prepareTestData();

    thrown.expect(IllegalArgumentException.class);
    userService.fetchUser(1234);
}

This approach is similar to try-catch and @Test(expected = ...), but we can control from which point the exception is expected.

assertThrows

AssertJ and JUnit 5 provide methods to assert that a specific code block throws a specific exception.

@Test
public void testInvalidData() {
    prepareTestData();

    Assertions.assertThrows(IllegalArgumentException.class, () -> {
        userService.fetchUser(1234);
    });
}

Assertions.assertThrows also returns the exception object to execute further asserts, e.g. to assert the message.

Summary

I try to use assertThrows as often as possible because the test code gets both readable and flexible. But all the other mentioned approaches are also valid if correctly used.

expectedExceptions comes from Test class family, assertThrows comes from Assert class family. Both will fail if the expected exception does not throw from the method in test.

Doc for expectedExceptions:

/**
 * The list of exceptions that a test method is expected to throw.  If no
 * exception or a different than one on this list is thrown, this test will be
 * marked a failure.
 */
public Class[] expectedExceptions() default {};

Doc for assertThrows

/**
 * Asserts that {@code runnable} throws an exception when invoked. If it does not, an
 * {@link AssertionError} is thrown.
 *
 * @param runnable A function that is expected to throw an exception when invoked
 * @since 6.9.5
 */

Both of them translates to:

try {
        ....
} catch (SomeException e) {

}

expectedExceptions can take more than one Exception instances. assertThrows() can be used as a replacement for expectedException though (it is latest). It makes more sense when you have to continue after assertThrows.

Related