Mockito 在异常之后验证 Junit 4.10

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

@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中放入什么方法,我的测试都通过了,这不是我想要的。

知道为什么会这样吗?

75930 次浏览

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

更新: 使用 Java8的 lambda 表达式,您可以在 try 块 足够简洁有用中包装函数式接口调用。我认为对这种语法的支持会在许多标准测试库中得到应用。

assertThrows(MyException.class,
() -> systemUnderTest.throwingMethod());

我还没有尝试过这种方法,但是除了 Jeff Bowman 的出色回答之外,您还可以选择使用 ExpectedException Rule 和 try... finally 结构,将您的验证语句放在 finally 块中。

更优雅的解决方案与 Catch 异常

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

一旦在 UT 引发 Exception,以下所有代码将被忽略。

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

为了对付这个问题并验证所有的调用,我们可以使用 试试看终于

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