481

Is there a better way to assert that a method throws an exception in JUnit 5?

Currently, I have to use an @Rule in order to verify that my test throws an exception, but this doesn't work for the cases where I expect multiple methods to throw exceptions in my test.

user3601487
  • 1,057
  • 1
  • 11
  • 21
steventrouble
  • 6,641
  • 3
  • 16
  • 19
  • 2
    you might be interested to check AssertJ for checking exceptions at it is more flexible than JUnit5 – user1075613 Feb 11 '20 at 18:15
  • Here is a nice example on [how assert that an exception is Thrown](https://www.codingeek.com/tutorials/junit/assert-exception-thrown-junit/) it in JUnit4 and JUnit5 – Hitesh Garg Apr 12 '21 at 18:22
  • If you are expecting multiple methods to throw exceptions in one test that's a code smell; you probably want to write multiple tests, one for each exception thrown – Ilario Apr 22 '21 at 09:41

12 Answers12

866

You can use assertThrows(), which allows you to test multiple exceptions within the same test. With support for lambdas in Java 8, this is the canonical way to test for exceptions in JUnit.

Per the JUnit docs:

import static org.junit.jupiter.api.Assertions.assertThrows;

@Test
void exceptionTesting() {
    MyException thrown = assertThrows(
           MyException.class,
           () -> myObject.doThing(),
           "Expected doThing() to throw, but it didn't"
    );

    assertTrue(thrown.getMessage().contains("Stuff"));
}
steventrouble
  • 6,641
  • 3
  • 16
  • 19
  • 23
    From an old-school "I dont know much about Junit5 and probably not enough about Java8" ... this looks rather bizarre. Would you mind adding some more explanations; like "which part in there is the actual 'production code' under test ... that would be supposed to throw"? – GhostCat Oct 26 '16 at 17:43
  • 3
    `() ->` _points_ to a lambda expression that accepts zero arguments. Thus, the "production code" that is expected to throw the exception is in the code block _pointed to_ (i.e., the `throw new...` statement within the curly brackets). – Sam Brannen Oct 27 '16 at 15:03
  • 3
    Typically the lambda expression would interact with the subject under test (SUT). In other words, directly throwing an exception like above is just for demonstration purposes. – Sam Brannen Oct 27 '16 at 15:04
  • 1
    Looks like expectThrows is deprecated. Docs say to use assertThrows() instead now. – depsypher Mar 14 '17 at 17:36
  • 5
    As of version 5.0.0-M4 **expectThrows** is no any longer available. Only **assertThrows** is allowed. See https://github.com/junit-team/junit5/blob/master/documentation/src/docs/asciidoc/release-notes-5.0.0-M4.adoc: 'Removed deprecated Assertions.expectThrows() method in favor of Assertions.assertThrows()' – gil.fernandes May 24 '17 at 10:29
  • 1
    FYI I had to use `() -> new IllegalArgumentException("a message");` (without the `throw` keyword), as the compiler didn't like that for some reason. – Danny Bullis Apr 05 '18 at 19:41
  • It would remove redundancy in the code example shown above to have `thrown` of type `Throwable` instead of `MyException`. – user3224237 Jul 17 '19 at 13:52
  • @user3224237 But it wouldn't demonstrate that `assertThrows` returns type `T extends Throwable` (in this case `MyException`) and not just a generic throwable. – Druckles Dec 09 '19 at 13:26
  • 5
    Jupiter is so lame to force us to assert the message this way :-/ – Julien Jun 25 '20 at 15:52
  • 1
    Is there a way I can verify exception message in assertThrows itself ? I dont want to use another variable to catch the exception and then use another assertTrue to check the message. – Ayush Dec 23 '20 at 03:02
  • 1
    Awesome, thanks, I was missing the point that it can return a value which I can verify further! – RAM237 Aug 12 '21 at 14:32
  • It's a good solution, it would be better with a assertEquals("Stuff",thrown.getMessage()) – Dubas Sep 01 '22 at 16:54
  • I'd avoid AssertEquals for error strings. It makes tests brittle because it's testing the entire error string, which could change. If you own the impl, it also makes it harder for the impl to return detailed error messages. – steventrouble Jun 04 '23 at 15:28
155

In Java 8 and JUnit 5 (Jupiter) we can assert for exceptions as follows. Using org.junit.jupiter.api.Assertions.assertThrows

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

Asserts that execution of the supplied executable throws an exception of the expectedType and returns the exception.

If no exception is thrown, or if an exception of a different type is thrown, this method will fail.

If you do not want to perform additional checks on the exception instance, simply ignore the return value.

@Test
public void itShouldThrowNullPointerExceptionWhenBlahBlah() {
    assertThrows(NullPointerException.class,
            ()->{
            //do whatever you want to do here
            //ex : objectName.thisMethodShoulThrowNullPointerExceptionForNullParameter(null);
            });
}

That approach will use the Functional Interface Executable in org.junit.jupiter.api.

Refer :

prime
  • 14,464
  • 14
  • 99
  • 131
  • 2
    To the top with this one! This is the best answer by far that's the most up-to-date with JUnit 5. Also, IntelliJ is condensing the lambda down even further if there's just one line to the Lambda: `assertThrows(NoSuchElementException.class, myLinkedList::getFirst);` – fIwJlxSzApHEZIl Aug 15 '18 at 14:45
  • @anon58192932 this doesn't happen because there's just one line, it's because you only call one method (although that would, naturally, also only be one line automatically, unless you have a mile-long method name). In other words: `object -> object.methodCall()` can be replaced with `Object::methodCall`, but you couldn't replace `object -> object.getOneThing().getAnotherThing().doSomething()` with a method call. – PixelMaster Sep 23 '20 at 16:39
44

They've changed it in JUnit 5 (expected: InvalidArgumentException, actual: invoked method) and code looks like this one:

@Test
public void wrongInput() {
    Throwable exception = assertThrows(InvalidArgumentException.class,
            ()->{objectName.yourMethod("WRONG");} );
}
jstar
  • 847
  • 9
  • 9
  • Thanks for showing that assertThrows returns the expected exception. That's very useful for further checks. – Sebastian Apr 06 '23 at 07:18
34

TL;DR: If you are on JUnit 5.8.0+ version, you should use assertThrowsExactly() instead of assertThrows() to match the exact exception type.

assertThrowsExactly(FileNotFoundException.class, () -> service.blah());

You can use assertThrows(), But with assertThrows your assertion will pass even if the thrown exception is of child type.

This is because, JUnit 5 checks exception type by calling Class.isIntance(..), Class.isInstance(..) will return true even if the thrown exception is of a child type.

The workaround for this is to assert on Class:

Throwable throwable =  assertThrows(Throwable.class, () -> {
    service.readFile("sampleFile.txt");
});
assertEquals(FileNotFoundException.class, throwable.getClass());
Govinda Sakhare
  • 5,009
  • 6
  • 33
  • 74
30

Now Junit5 provides a way to assert the exceptions

You can test both general exceptions and customized exceptions

A general exception scenario:

ExpectGeneralException.java

public void validateParameters(Integer param ) {
    if (param == null) {
        throw new NullPointerException("Null parameters are not allowed");
    }
}

ExpectGeneralExceptionTest.java

@Test
@DisplayName("Test assert NullPointerException")
void testGeneralException(TestInfo testInfo) {
    final ExpectGeneralException generalEx = new ExpectGeneralException();

     NullPointerException exception = assertThrows(NullPointerException.class, () -> {
            generalEx.validateParameters(null);
        });
    assertEquals("Null parameters are not allowed", exception.getMessage());
}

You can find a sample to test CustomException here : assert exception code sample

ExpectCustomException.java

public String constructErrorMessage(String... args) throws InvalidParameterCountException {
    if(args.length!=3) {
        throw new InvalidParameterCountException("Invalid parametercount: expected=3, passed="+args.length);
    }else {
        String message = "";
        for(String arg: args) {
            message += arg;
        }
        return message;
    }
}

ExpectCustomExceptionTest.java

@Test
@DisplayName("Test assert exception")
void testCustomException(TestInfo testInfo) {
    final ExpectCustomException expectEx = new ExpectCustomException();

     InvalidParameterCountException exception = assertThrows(InvalidParameterCountException.class, () -> {
            expectEx.constructErrorMessage("sample ","error");
        });
    assertEquals("Invalid parametercount: expected=3, passed=2", exception.getMessage());
}
Anupama Boorlagadda
  • 1,158
  • 1
  • 11
  • 19
23

You can use assertThrows(). My example is taken from the docs http://junit.org/junit5/docs/current/user-guide/

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

....

@Test
void exceptionTesting() {
    Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
        throw new IllegalArgumentException("a message");
    });
    assertEquals("a message", exception.getMessage());
}
Will Humphreys
  • 2,677
  • 3
  • 25
  • 26
11

I think this is an even simpler example

List<String> emptyList = new ArrayList<>();
Optional<String> opt2 = emptyList.stream().findFirst();
assertThrows(NoSuchElementException.class, () -> opt2.get());

Calling get() on an optional containing an empty ArrayList will throw a NoSuchElementException. assertThrows declares the expected exception and provides a lambda supplier (takes no arguments and returns a value).

Thanks to @prime for his answer which I hopefully elaborated on.

John R Perry
  • 3,916
  • 2
  • 38
  • 62
JesseBoyd
  • 1,022
  • 1
  • 13
  • 18
  • 1
    the method `assertThrows` returns the thrown exception. So you can do like `NoSuchElementException e = assertThrows(NoSuchElementException.class, () -> opt2.get());` then below you can do whatever sort of assertions on the exception object you want. – Captain Man Jul 23 '18 at 15:49
6

An even simpler one liner. No lambda expressions or curly braces required for this example using Java 8 and JUnit 5

import static org.junit.jupiter.api.Assertions.assertThrows;

@Test
void exceptionTesting() {

    assertThrows(MyException.class, myStackObject::doStackAction, "custom message if assertion fails..."); 

// note, no parenthesis on doStackAction ex ::pop NOT ::pop()
}
rnyunja
  • 219
  • 3
  • 4
2

My solution:

    protected <T extends Throwable> void assertExpectedException(ThrowingRunnable methodExpectedToFail, Class<T> expectedThrowableClass,
        String expectedMessage) {
    T exception = assertThrows(expectedThrowableClass, methodExpectedToFail);
    assertEquals(expectedMessage, exception.getMessage());
}

And you can call it like this:

    assertExpectedException(() -> {
        carService.findById(id);
    }, IllegalArgumentException.class, "invalid id");
István D
  • 152
  • 1
  • 9
2

This is what I do when testing to make sure an exception has been thrown

//when
final var tripConsumer = new BusTripConsumer(inputStream);
final Executable executable = () -> tripConsumer.deserialiseTripData();

//then
assertThrows(IllegalArgumentException.class, executable);
ggorlen
  • 44,755
  • 7
  • 76
  • 106
Damien Cooke
  • 599
  • 10
  • 20
1

Actually I think there is a error in the documentation for this particular example. The method that is intended is expectThrows

public static void assertThrows(
public static <T extends Throwable> T expectThrows(
Peter
  • 5,556
  • 3
  • 23
  • 38
  • 3
    "Removed deprecated Assertions.expectThrows() method in favor of Assertions.assertThrows()." – Martin Schröder Feb 07 '18 at 12:29
  • 2
    For Junit 5, make sure it's from org.junit.jupiter.api.Assertions not org.testng.Assert. Our project has both Junit and TestNG included, and I kept getting assertThrows returns void error until I changed it to assertExpects. It turned out that I was using org.testng.Assert. – barryku Jun 16 '20 at 18:51
-10

Here is an easy way.

@Test
void exceptionTest() {

   try{
        model.someMethod("invalidInput");
        fail("Exception Expected!");
   }
   catch(SpecificException e){

        assertTrue(true);
   }
   catch(Exception e){
        fail("wrong exception thrown");
   }

}

It only succeeds when the Exception you expect is thrown.

kiwicomb123
  • 1,503
  • 21
  • 26