JUnit 5:如何断言是否抛出异常?

497

在JUnit 5中,有更好的方法来断言方法是否抛出异常吗?

目前,我必须使用@Rule来验证我的测试是否抛出异常,但是这对于期望在我的测试中有多个方法抛出异常的情况不起作用。


2
你可能会对检查异常更灵活的AssertJ感兴趣,它比JUnit5更加灵活。 - user1075613
这里有一个很好的例子,介绍如何在JUnit4和JUnit5中使用assert来断言异常是否被抛出:[https://www.codingeek.com/tutorials/junit/assert-exception-thrown-junit/] - Hitesh Garg
如果您期望一个测试中有多个方法抛出异常,那么这是一种代码异味;您可能希望编写多个测试,每个测试针对抛出的异常。 - Ilario
12个回答

892
你可以使用assertThrows(),它允许你在同一个测试中测试多个异常。在Java 8中支持lambda表达式的情况下,这是JUnit中测试异常的标准方式。
根据JUnit文档的说明:
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"));
}

23
从一个老派的“我对Junit5不太了解,可能对Java8也不够熟悉”...这看起来相当奇怪。你介意加一些更多的解释吗?比如,“在那里的哪个部分是实际正在测试的‘生产代码’...应该会抛出什么异常?” - GhostCat
3
() ->指向一个接受零个参数的lambda表达式。因此,预计抛出异常的"生产代码"位于所指向的代码块中(即花括号内的throw new...语句)。 - Sam Brannen
3
通常,λ表达式会与被测试的主题(SUT)进行交互。换句话说,直接像上面那样抛出异常只是为了演示目的。 - Sam Brannen
5
从版本5.0.0-M4开始,expectThrows方法不再可用。只允许使用assertThrows方法。请参见https://github.com/junit-team/junit5/blob/master/documentation/src/docs/asciidoc/release-notes-5.0.0-M4.adoc:'已删除过时的Assertions.expectThrows() 方法,改用Assertions.assertThrows()'。 - gil.fernandes
5
木星真是太糟糕了,居然迫使我们用这种方式来确认信息 :-/ - Julien
显示剩余8条评论

159
在Java 8和JUnit 5(Jupiter)中,我们可以使用org.junit.jupiter.api.Assertions.assertThrows来断言异常。

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

断言提供的可执行代码会抛出预期类型的异常,并返回该异常。

如果没有抛出异常或者抛出了不同类型的异常,此方法将失败。

如果您不想对异常实例执行其他检查,请简单地忽略返回值。

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

那种方法将使用Functional Interface Executableorg.junit.jupiter.api中。
参考:

2
用这个答案顶上去! 这绝对是到目前为止最更新的关于JUnit 5的最佳答案。此外,如果Lambda只有一行,IntelliJ甚至可以将其压缩为:assertThrows(NoSuchElementException.class, myLinkedList::getFirst); - fIwJlxSzApHEZIl
@anon58192932,这不是因为只有一行代码,而是因为你只调用了一个方法(尽管这也只会自动成为一行代码,除非你的方法名很长)。换句话说:object -> object.methodCall() 可以被替换为 Object::methodCall,但你不能用一个方法调用来替换 object -> object.getOneThing().getAnotherThing().doSomething() - PixelMaster
感谢使用 Assertions.assertThrows()。 - CoolMind

44
他们在JUnit 5中进行了更改(期望值:InvalidArgumentException,实际值:调用的方法),代码如下:
@Test
public void wrongInput() {
    Throwable exception = assertThrows(InvalidArgumentException.class,
            ()->{objectName.yourMethod("WRONG");} );
}

感谢您展示了assertThrows返回预期异常。这对进一步的检查非常有用。 - Sebastian

35
如果你使用的是JUnit 5.8.0+版本,那么你应该使用assertThrowsExactly()而不是assertThrows()来匹配精确的异常类型。
assertThrowsExactly(FileNotFoundException.class, () -> service.blah());

你可以使用assertThrows(),但是使用assertThrows时,即使抛出的异常是子类型,你的断言也会通过。
这是因为JUnit 5通过调用Class.isInstance(..)来检查异常类型,Class.isInstance(..)会返回true,即使抛出的异常是子类型。
解决这个问题的方法是对类进行断言。
Throwable throwable =  assertThrows(Throwable.class, () -> {
    service.readFile("sampleFile.txt");
});
assertEquals(FileNotFoundException.class, throwable.getClass());

30

Junit5现在提供了一种断言异常的方式。

您可以测试常规异常和自定义异常。

一个常规异常场景:

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());
}

您可以在此处找到用于测试CustomException的示例:断言异常代码示例

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());
}

1
JUnit 如何处理内建和自定义的异常没有区别。 - raindev

23
您可以使用assertThrows()。我的示例来自文档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());
}

11

我认为这是一个更简单的例子。

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

在包含空 ArrayList 的可选项上调用 get() 将抛出 NoSuchElementExceptionassertThrows 声明了预期的异常并提供了一个 lambda 供应商(不带参数且返回一个值)。

感谢 @prime 的答案,我希望我对其进行了详细说明。


1
方法assertThrows返回抛出的异常。因此,您可以像这样操作:NoSuchElementException e = assertThrows(NoSuchElementException.class, () -> opt2.get()); 然后在下面,您可以对异常对象进行任何类型的断言。 - Captain Man

6
一个更简单的一行式代码,使用Java 8和JUnit 5,不需要lambda表达式或大括号。
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()
}

2

这是我在测试时确保已抛出异常的操作

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

//then
assertThrows(IllegalArgumentException.class, executable);

2

我的解决方案:

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

您可以这样调用:

    assertExpectedException(() -> {
        carService.findById(id);
    }, IllegalArgumentException.class, "invalid id");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接