77

我正在测试一个带有预期异常的方法。我还需要验证在抛出异常后(在模拟对象上)调用了一些清理代码,但看起来验证被忽略了。这是代码。我正在使用 JunitExpectedException Rule来验证预期的异常。

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

@Test
public void testExpectedException()
{
   MockedObject mockObj = mock(MockedObj.class);
   MySubject subject = new MySubject(mockedObj);
   expectedEx.expect(MyException.class);
   expectedEx.expectMessage("My exception message.");
   subject.someMethodThrowingException();
   verify(mockObj).
       someCleanup(eq(...));
}

似乎verify完全被忽略了。无论我放入什么方法verify,我的测试都通过了,这不是我想要的。

知道为什么会这样吗?

4

4 回答 4

87

ExpectedException通过JUnit @Rule将整个测试方法包装在 try-catch 块中。当您的代码抛出异常时,它会向上堆栈到最近的 try/catch,这恰好位于 ExpectedException 实例中(它会检查它是否是您期望的异常)。

在 Java 中,如果方法中发生未捕获的异常,则控制权将永远不会返回到该方法稍后的语句。相同的规则在这里适用:在异常发生后,控制永远不会返回到测试中的语句。

从技术上讲,您可以将验证放在 finally 块中,但这往往是一个坏习惯编辑:您的被测系统可能会抛出意外异常,或者根本没有异常,这将为您提供有用的失败消息和跟踪;但是,如果该失败导致您的验证或断言在finally块中失败,那么 Java 将显示该错误,而不是有关意外异常或意外成功的消息。这会使调试变得困难,特别是因为您的错误将来自错误根本原因之后的代码行,错误地暗示上面的代码成功了。

如果你真的需要在异常发生后验证状态,基于每个方法,你总是可以恢复到这个习惯用法:

@Test
public void testExpectedException()
{
  MockedObject mockObj = mock(MockedObj.class);
  MySubject subject = new MySubject(mockedObj);
  try {
    subject.someMethodThrowingException();
    fail("Expected MyException.");
  } catch (MyException expected) {
    assertEquals("My exception message.", expected.getMessage());
  }
  verify(mockObj).someCleanup(eq(...));
}

更新:使用 Java 8 的 lambda 表达式,您可以在 try 块中简洁地包装函数式接口调用,以使其有用。我想对这种语法的支持将进入许多标准测试库。

assertThrows(MyException.class,
    () -> systemUnderTest.throwingMethod());
于 2012-11-05T00:48:56.403 回答
10

一旦在 UT 中抛出异常,下面的所有代码都将被忽略。

@Test(expected = Exception.class)
public void testExpectedException() {
   MockedObject mockObj = mock(MockedObj.class);
   MySubject subject = new MySubject(mockedObj);
   subject.doSomething(); // If this line results in an exception then all the code below this will be ignored.
   subject.someMethodThrowingException();
   verify(mockObj).
       someCleanup(eq(...));
}

为了解决这个问题并验证所有调用,我们可以使用tryfinally

@Test(expected = Exception.class)
    public void testExpectedException() {
          MockedObject mockObj = mock(MockedObj.class);
          MySubject subject = new MySubject(mockedObj);
          try {
               subject.someMethodThrowingException(); 
          } finally {
             verify(mockObj).
             someCleanup(eq(...));
          }
} 
于 2019-05-01T16:24:37.013 回答
5

带有捕获异常的更优雅的解决方案

@Test
public void testExpectedException()
{
    MockedObject mockObj = mock(MockedObject.class);
    MySubject subject = new MySubject(mockObj);

    when(subject).someMethodThrowingException();

    then(caughtException())
            .isInstanceOf(MyException.class)
            .hasMessage("My exception message.");

    verify(mockObj).someCleanup(eq(...));
}
于 2013-11-21T21:45:52.563 回答
4

我还没有尝试过,但是除了 Jeff Bowman 的出色回答之外,您可能还可以选择使用带有 try...finally 构造的 ExpectedException 规则,将您的验证语句放在 finally 块中。

于 2012-11-05T02:59:29.550 回答