如何使用 JUnit Test 注释断言异常消息?

发布于 2024-08-25 19:11:57 字数 381 浏览 8 评论 0原文

我用 @Test 注释编写了一些 JUnit 测试。如果我的测试方法抛出一个已检查的异常,并且我想断言消息和异常,有没有办法使用 JUnit @Test 注释来做到这一点? AFAIK,JUnit 4.7 不提供此功能,但未来的版本是否提供此功能?我知道在 .NET 中您可以断言消息和异常类。在 Java 世界中寻找类似的功能。

这就是我想要的:

@Test (expected = RuntimeException.class, message = "Employee ID is null")
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() {}

I have written a few JUnit tests with @Test annotation. If my test method throws a checked exception and if I want to assert the message along with the exception, is there a way to do so with JUnit @Test annotation? AFAIK, JUnit 4.7 doesn't provide this feature but does any future versions provide it? I know in .NET you can assert the message and the exception class. Looking for similar feature in the Java world.

This is what I want:

@Test (expected = RuntimeException.class, message = "Employee ID is null")
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() {}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

魔法少女 2024-09-01 19:11:57

您可以使用 @Rule 注释ExpectedException,如下所示

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

@Test
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() throws Exception {
    expectedEx.expect(RuntimeException.class);
    expectedEx.expectMessage("Employee ID is null");

    // do something that should throw the exception...
    System.out.println("=======Starting Exception process=======");
    throw new NullPointerException("Employee ID is null");
}

ExpectedException 文档中的示例(当前)是错误的 - 没有公共构造函数,因此您必须使用 ExpectedException.none()

You could use the @Rule annotation with ExpectedException, like this:

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

@Test
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() throws Exception {
    expectedEx.expect(RuntimeException.class);
    expectedEx.expectMessage("Employee ID is null");

    // do something that should throw the exception...
    System.out.println("=======Starting Exception process=======");
    throw new NullPointerException("Employee ID is null");
}

Note that the example in the ExpectedException docs is (currently) wrong - there's no public constructor, so you have to use ExpectedException.none().

别靠近我心 2024-09-01 19:11:57

在 JUnit 4.13 中,您可以执行以下操作:

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;

...

@Test
void exceptionTesting() {
  IllegalArgumentException exception = assertThrows(
    IllegalArgumentException.class, 
    () -> { throw new IllegalArgumentException("a message"); }
  );

  assertEquals("a message", exception.getMessage());
}

这也适用于 JUnit 5< /a> 但具有不同的导入:

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

...

In JUnit 4.13 you can do:

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;

...

@Test
void exceptionTesting() {
  IllegalArgumentException exception = assertThrows(
    IllegalArgumentException.class, 
    () -> { throw new IllegalArgumentException("a message"); }
  );

  assertEquals("a message", exception.getMessage());
}

This also works in JUnit 5 but with different imports:

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

...
独享拥抱 2024-09-01 19:11:57

我喜欢 @Rule 答案。但是,如果由于某种原因您不想使用规则。还有第三种选择。

@Test (expected = RuntimeException.class)
public void myTestMethod()
{
   try
   {
      //Run exception throwing operation here
   }
   catch(RuntimeException re)
   {
      String message = "Employee ID is null";
      assertEquals(message, re.getMessage());
      throw re;
    }
    fail("Employee Id Null exception did not throw!");
  }

I like the @Rule answer. However, if for some reason you don't want to use rules. There is a third option.

@Test (expected = RuntimeException.class)
public void myTestMethod()
{
   try
   {
      //Run exception throwing operation here
   }
   catch(RuntimeException re)
   {
      String message = "Employee ID is null";
      assertEquals(message, re.getMessage());
      throw re;
    }
    fail("Employee Id Null exception did not throw!");
  }
蘑菇王子 2024-09-01 19:11:57

您必须使用@Test(expected=SomeException.class)吗?当我们必须断言异常的实际消息时,这就是我们所做的。

@Test
public void myTestMethod()
{
  try
  {
    final Integer employeeId = null;
    new Employee(employeeId);
    fail("Should have thrown SomeException but did not!");
  }
  catch( final SomeException e )
  {
    final String msg = "Employee ID is null";
    assertEquals(msg, e.getMessage());
  }
}

Do you have to use @Test(expected=SomeException.class)? When we have to assert the actual message of the exception, this is what we do.

@Test
public void myTestMethod()
{
  try
  {
    final Integer employeeId = null;
    new Employee(employeeId);
    fail("Should have thrown SomeException but did not!");
  }
  catch( final SomeException e )
  {
    final String msg = "Employee ID is null";
    assertEquals(msg, e.getMessage());
  }
}
忆依然 2024-09-01 19:11:57

实际上,最好的用法是与 try/catch 一起使用。为什么?因为你可以控制你期望发生异常的地方。

考虑这个例子:

@Test (expected = RuntimeException.class)
public void someTest() {
   // test preparation
   // actual test
}

如果有一天代码被修改并且测试准备将抛出 RuntimeException 怎么办?在这种情况下,实际测试甚至没有经过测试,即使它没有抛出任何异常,测试也会通过。

这就是为什么使用 try/catch 比依赖注释要好得多。

Actually, the best usage is with try/catch. Why? Because you can control the place where you expect the exception.

Consider this example:

@Test (expected = RuntimeException.class)
public void someTest() {
   // test preparation
   // actual test
}

What if one day the code is modified and test preparation will throw a RuntimeException? In that case actual test is not even tested and even if it doesn't throw any exception the test will pass.

That is why it is much better to use try/catch than to rely on the annotation.

2024-09-01 19:11:57

我从来不喜欢使用 Junit 断言异常的方式。如果我在注释中使用“预期”,从我的角度来看,我们似乎违反了“给定、何时、然后”模式,因为“然后”被放置在测试定义的顶部。

另外,如果我们使用“@Rule”,我们就必须处理如此多的样板代码。所以,如果你可以为你的测试安装新的库,我建议看看 AssertJ (该库现在随 SpringBoot 一起提供)

然后一个不违反“given/when/then”原则的测试,它使用 AssertJ 来验证:

1 - 异常是我们所期望的。
2 - 它还有一条预期消息,

如下所示:

 @Test
void should_throwIllegalUse_when_idNotGiven() {

    //when
    final Throwable raisedException = catchThrowable(() -> getUserDAO.byId(null));

    //then
    assertThat(raisedException).isInstanceOf(IllegalArgumentException.class)
            .hasMessageContaining("Id to fetch is mandatory");
}

I never liked the way of asserting exceptions with Junit. If I use the "expected" in the annotation, seems from my point of view we're violating the "given, when, then" pattern because the "then" is placed at the top of the test definition.

Also, if we use "@Rule", we have to deal with so much boilerplate code. So, if you can install new libraries for your tests, I'd suggest to have a look to the AssertJ (that library now comes with SpringBoot)

Then a test which is not violating the "given/when/then" principles, and it is done using AssertJ to verify:

1 - The exception is what we're expecting.
2 - It has also an expected message

Will look like this:

 @Test
void should_throwIllegalUse_when_idNotGiven() {

    //when
    final Throwable raisedException = catchThrowable(() -> getUserDAO.byId(null));

    //then
    assertThat(raisedException).isInstanceOf(IllegalArgumentException.class)
            .hasMessageContaining("Id to fetch is mandatory");
}
椵侞 2024-09-01 19:11:57

Raystorm 有一个很好的答案。我也不太喜欢规则。我做了类似的事情,只是我创建了以下实用程序类来帮助提高可读性和可用性,这首先是注释的一大优点。

添加此实用程序类:

import org.junit.Assert;

public abstract class ExpectedRuntimeExceptionAsserter {

    private String expectedExceptionMessage;

    public ExpectedRuntimeExceptionAsserter(String expectedExceptionMessage) {
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run(){
        try{
            expectException();
            Assert.fail(String.format("Expected a RuntimeException '%s'", expectedExceptionMessage));
        } catch (RuntimeException e){
            Assert.assertEquals("RuntimeException caught, but unexpected message", expectedExceptionMessage, e.getMessage());
        }
    }

    protected abstract void expectException();

}

然后对于我的单元测试,我只需要以下代码:

@Test
public void verifyAnonymousUserCantAccessPrivilegedResourceTest(){
    new ExpectedRuntimeExceptionAsserter("anonymous user can't access privileged resource"){
        @Override
        protected void expectException() {
            throw new RuntimeException("anonymous user can't access privileged resource");
        }
    }.run(); //passes test; expected exception is caught, and this @Test returns normally as "Passed"
}

Raystorm had a good answer. I'm not a big fan of Rules either. I do something similar, except that I create the following utility class to help readability and usability, which is one of the big plus'es of annotations in the first place.

Add this utility class:

import org.junit.Assert;

public abstract class ExpectedRuntimeExceptionAsserter {

    private String expectedExceptionMessage;

    public ExpectedRuntimeExceptionAsserter(String expectedExceptionMessage) {
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run(){
        try{
            expectException();
            Assert.fail(String.format("Expected a RuntimeException '%s'", expectedExceptionMessage));
        } catch (RuntimeException e){
            Assert.assertEquals("RuntimeException caught, but unexpected message", expectedExceptionMessage, e.getMessage());
        }
    }

    protected abstract void expectException();

}

Then for my unit test, all I need is this code:

@Test
public void verifyAnonymousUserCantAccessPrivilegedResourceTest(){
    new ExpectedRuntimeExceptionAsserter("anonymous user can't access privileged resource"){
        @Override
        protected void expectException() {
            throw new RuntimeException("anonymous user can't access privileged resource");
        }
    }.run(); //passes test; expected exception is caught, and this @Test returns normally as "Passed"
}
自控 2024-09-01 19:11:57

假设我们在 FooService 类中有一个方法 doStuff。当参数中传递 null 标志时,此方法可能会引发 NullPointerException

public class FooService {

  public void doStuff(Boolean flag) {
    try{
      if(flag){
        // do stuff
      }
    }catch (Exception e){
      throw new RuntimeException("Unexpected error occurred", e);
    }
  }
}

使用 Junit 5

Junit 5 assertThrows 返回异常,可用于断言异常消息,如下例所示: -

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

@Test
public void doStuff_testThrownException(){   
    // null is passed, expected NullPointerException
    Throwable exception = assertThrows(RuntimeException.class, () -> fooService.doStuff(null)); 

    assertEquals("Unexpected error occurred", exception.getMessage());
}

使用 AssertJ

AssertJ 有各种断言来测试异常,其中之一是 assertThatRuntimeException 可以与 withMessage 链接来测试异常消息

import static org.assertj.core.api.Assertions.*;

@Test
public void doStuff_testThrownException(){
  // null is passed, expected NullPointerException
  assertThatRuntimeException().isThrownBy(() -> fooService.doStuff(null))
          .withMessage("Unexpected error occurred");
}

来源:CodingNConcepts

Let's say, we have a method doStuff in the class FooService. This method can throw a NullPointerException when a null flag is passed in the argument.

public class FooService {

  public void doStuff(Boolean flag) {
    try{
      if(flag){
        // do stuff
      }
    }catch (Exception e){
      throw new RuntimeException("Unexpected error occurred", e);
    }
  }
}

Using Junit 5

Junit 5 assertThrows returns exception, which can be used to assert exception message as per below example:-

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

@Test
public void doStuff_testThrownException(){   
    // null is passed, expected NullPointerException
    Throwable exception = assertThrows(RuntimeException.class, () -> fooService.doStuff(null)); 

    assertEquals("Unexpected error occurred", exception.getMessage());
}

Using AssertJ

AssertJ has various assertions to test exception, one of them is assertThatRuntimeException which can be chained with withMessage to test exception message

import static org.assertj.core.api.Assertions.*;

@Test
public void doStuff_testThrownException(){
  // null is passed, expected NullPointerException
  assertThatRuntimeException().isThrownBy(() -> fooService.doStuff(null))
          .withMessage("Unexpected error occurred");
}

Source: CodingNConcepts

又怨 2024-09-01 19:11:57

我更喜欢 AssertJ 。

        assertThatExceptionOfType(ExpectedException.class)
        .isThrownBy(() -> {
            // method call
        }).withMessage("My message");

I would prefer AssertJ for this.

        assertThatExceptionOfType(ExpectedException.class)
        .isThrownBy(() -> {
            // method call
        }).withMessage("My message");
风筝在阴天搁浅。 2024-09-01 19:11:57

如果使用 @Rule,则异常集将应用于 Test 类中的所有测试方法。

If using @Rule, the exception set is applied to all the test methods in the Test class.

陪我终i 2024-09-01 19:11:57

我喜欢 user64141 的答案,但发现它可以更普遍。这是我的看法:

public abstract class ExpectedThrowableAsserter implements Runnable {

    private final Class<? extends Throwable> throwableClass;
    private final String expectedExceptionMessage;

    protected ExpectedThrowableAsserter(Class<? extends Throwable> throwableClass, String expectedExceptionMessage) {
        this.throwableClass = throwableClass;
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run() {
        try {
            expectException();
        } catch (Throwable e) {
            assertTrue(String.format("Caught unexpected %s", e.getClass().getSimpleName()), throwableClass.isInstance(e));
            assertEquals(String.format("%s caught, but unexpected message", throwableClass.getSimpleName()), expectedExceptionMessage, e.getMessage());
            return;
        }
        fail(String.format("Expected %s, but no exception was thrown.", throwableClass.getSimpleName()));
    }

    protected abstract void expectException();

}

请注意,在 try 块中保留“fail”语句会导致相关的断言异常被捕获;在 catch 语句中使用 return 可以防止这种情况发生。

I like user64141's answer but found that it could be more generalized. Here's my take:

public abstract class ExpectedThrowableAsserter implements Runnable {

    private final Class<? extends Throwable> throwableClass;
    private final String expectedExceptionMessage;

    protected ExpectedThrowableAsserter(Class<? extends Throwable> throwableClass, String expectedExceptionMessage) {
        this.throwableClass = throwableClass;
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run() {
        try {
            expectException();
        } catch (Throwable e) {
            assertTrue(String.format("Caught unexpected %s", e.getClass().getSimpleName()), throwableClass.isInstance(e));
            assertEquals(String.format("%s caught, but unexpected message", throwableClass.getSimpleName()), expectedExceptionMessage, e.getMessage());
            return;
        }
        fail(String.format("Expected %s, but no exception was thrown.", throwableClass.getSimpleName()));
    }

    protected abstract void expectException();

}

Note that leaving the "fail" statement within the try block causes the related assertion exception to be caught; using return within the catch statement prevents this.

轻拂→两袖风尘 2024-09-01 19:11:57

导入 catch-exception 库并使用它。它比 ExpectedException 规则或 try-catch 干净得多。

他们的文档示例:

import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers.*;

// given: an empty list
List myList = new ArrayList();

// when: we try to get the first element of the list
catchException(myList).get(1);

// then: we expect an IndexOutOfBoundsException with message "Index: 1, Size: 0"
assertThat(caughtException(),
  allOf(
    instanceOf(IndexOutOfBoundsException.class),
    hasMessage("Index: 1, Size: 0"),
    hasNoCause()
  )
);

Import the catch-exception library, and use that. It's much cleaner than the ExpectedException rule or a try-catch.

Example form their docs:

import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers.*;

// given: an empty list
List myList = new ArrayList();

// when: we try to get the first element of the list
catchException(myList).get(1);

// then: we expect an IndexOutOfBoundsException with message "Index: 1, Size: 0"
assertThat(caughtException(),
  allOf(
    instanceOf(IndexOutOfBoundsException.class),
    hasMessage("Index: 1, Size: 0"),
    hasNoCause()
  )
);
開玄 2024-09-01 19:11:57
@Test (expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "This is not allowed")
public void testInvalidValidation() throws Exception{
     //test code
}
@Test (expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "This is not allowed")
public void testInvalidValidation() throws Exception{
     //test code
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文