简体   繁体   English

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

[英]JUnit 5: How to assert an exception is thrown?

Is there a better way to assert that a method throws an exception in JUnit 5? 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.目前,我必须使用 @Rule 来验证我的测试是否抛出异常,但这不适用于我希望多个方法在我的测试中抛出异常的情况。

You can use assertThrows() , which allows you to test multiple exceptions within the same test.您可以使用assertThrows() ,它允许您在同一个测试中测试多个异常。 With support for lambdas in Java 8, this is the canonical way to test for exceptions in JUnit.在 Java 8 中支持 lambda,这是在 JUnit 中测试异常的规范方法。

Per the JUnit docs :根据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"));
}

In Java 8 and JUnit 5 (Jupiter) we can assert for exceptions as follows.在 Java 8 和 JUnit 5 (Jupiter) 中,我们可以断言异常如下。 Using org.junit.jupiter.api.Assertions.assertThrows使用org.junit.jupiter.api.Assertions.assertThrows

public static < T extends Throwable > T assertThrows(Class< T > expectedType, Executable executable) 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 .该方法将使用org.junit.jupiter.api中的功能接口Executable

Refer :参考 :

They've changed it in JUnit 5 (expected: InvalidArgumentException, actual: invoked method) and code looks like this one:他们在 JUnit 5 中对其进行了更改(预期:InvalidArgumentException,实际:调用方法),代码如下所示:

@Test
public void wrongInput() {
    Throwable exception = assertThrows(InvalidArgumentException.class,
            ()->{objectName.yourMethod("WRONG");} );
}

Now Junit5 provides a way to assert the exceptions现在 Junit5 提供了一种断言异常的方法

You can test both general exceptions and customized exceptions您可以测试一般异常和自定义异常

A general exception scenario:一般异常情况:

ExpectGeneralException.java ExpectGeneralException.java

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

ExpectGeneralExceptionTest.java 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您可以在此处找到测试 CustomException 的示例: 断言异常代码示例

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

TL;DR: If you are on JUnit 5.8.0+ version, you can use assertThrowsExactly() instead of assertThrows() to match the exact exception type. TL;DR:如果您使用的是 JUnit 5.8.0+ 版本,则可以使用assertThrowsExactly()而不是assertThrows()来匹配确切的异常类型。

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.您可以使用assertThrows() ,但使用assertThrows即使抛出的异常是子类型,您的断言也会通过。

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.这是因为,JUnit 5 通过调用Class.isIntance(..)来检查异常类型,即使抛出的异常是子类型, Class.isInstance(..)也会返回 true。

The workaround for this is to assert on Class:解决方法是在 Class 上断言:

Throwable throwable =  assertThrows(Throwable.class, () -> {
    service.readFile("sampleFile.txt");
});
assertEquals(FileNotFoundException.class, throwable.getClass());

You can use assertThrows() .您可以使用assertThrows() My example is taken from the docs http://junit.org/junit5/docs/current/user-guide/我的示例取自文档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());
}

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 .对包含空ArrayList的可选项调用get()将引发NoSuchElementException assertThrows declares the expected exception and provides a lambda supplier (takes no arguments and returns a value). assertThrows声明了预期的异常并提供了一个 lambda 供应商(不接受任何参数并返回一个值)。

Thanks to @prime for his answer which I hopefully elaborated on.感谢@prime 的回答,我希望能详细说明。

An even simpler one liner.一个更简单的班轮。 No lambda expressions or curly braces required for this example using Java 8 and JUnit 5此示例使用 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()
}

Actually I think there is a error in the documentation for this particular example.实际上,我认为此特定示例的文档中有错误。 The method that is intended is expectThrows预期的方法是 expectThrows

public static void assertThrows(
public static <T extends Throwable> T expectThrows(

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");
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);

There are 3 ways to assert a certain exception in Junit. 有三种方法可以在Junit中声明某个异常。 Let's write the unit test cases for it. 让我们为它编写单元测试用例。

1. try-catch idiom This idiom is one of the most popular ones because it was used already in JUnit 3. This approach is a common pattern. 1. try-catch成语这个成语是最流行的成语之一,因为它已经在JUnit 3中使用过。这种方法是一种常见的模式。 The test will fail when no exception is thrown and the exception itself is verified in a catch clause. 当没有抛出异常并且在catch子句中验证异常本身时,测试将失败。

@Test
public void convertIntoUpperCase_withInvalidInput_tryCatchIdiom() {
    try {
        exceptionHandling.convertIntoUpperCase("");
        fail("It should throw IllegalArgumentException");
    } catch (IllegalArgumentException e) {
        Assertions.assertThat(e)
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessage("Empty value is passed.");
    }
}

2. @Test expected annotation In this approach, we specify the expected exception in @Test as below @Test(expected = IllegalArgumentException.class) 2. @Test期望注释在这种方法中,我们在@Test中指定预期的异常,如下所示@Test(expected = IllegalArgumentException.class)

When the exception wasn't thrown you will get the following message: java.lang.AssertionError: Expected exception: java.lang.IllegalArgumentException 当没有抛出异常时,您将收到以下消息:java.lang.AssertionError:预期的异常:java.lang.IllegalArgumentException

With this approach, you need to be careful though. 使用这种方法,你需要小心。 Sometimes it is tempting to expect general Exception, RuntimeException or even a Throwable. 有时候很容易期待一般的Exception,RuntimeException甚至是Throwable。 And this is considered as a bad practice because your code may throw an exception in other places than you actually expected and your test will still pass! 这被认为是一种不好的做法,因为你的代码可能会在实际预期的其他地方抛出异常而你的测试仍然会通过!

One of the drawback of this approach is you can't assert for the exception message. 这种方法的缺点之一是您无法断言异常消息。

@Test(expected = IllegalArgumentException.class)
public void convertIntoUpperCase_withInvalidInput_testExpected() {
    exceptionHandling.convertIntoUpperCase("");
}

3. Junit @Rule The same example can be created using ExceptedException rule. 3. Junit @Rule可以使用ExceptedException规则创建相同的示例。 The rule must be a public field marked with @Rule annotation. 规则必须是标有@Rule注释的公共字段。

    @Test
    public void convertIntoUpperCase_withInvalidInput_ExpectedExceptionRule() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Empty value is passed.");
        exceptionHandling.convertIntoUpperCase("");
    }

I find the above code more readable hence I prefer to use this approach. 我发现上面的代码更具可读性,因此我更喜欢使用这种方法。

When the exception isn't thrown you will get the following message: java.lang.AssertionError: Expected test to throw (an instance of java.lang.IllegalArgumentException and exception with the message “Empty value is passed.”). 当没有抛出异常时,您将收到以下消息:java.lang.AssertionError:要抛出的预期测试(java.lang.IllegalArgumentException和异常的实例,并带有消息“传递空值。”)。 Pretty nice. 挺棒的。

But not all exceptions I check with the above approach. 但不是我用上述方法检查的所有例外情况。 Sometimes I need to check only the type of the exception thrown and then I use @Test annotation. 有时我只需检查抛出的异常类型,然后使用@Test注释。

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.只有当您期望的异常被抛出时,它才会成功。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM