如何验证被测代码中捕获的由Mock对象抛出的异常?
如何验证Mockito模拟的异常被主代码捕获?
在Java/Junit/Mockito环境中,模拟异常(比如doThrow())或验证未被捕获的异常(比如assertThrows())都很简单。但如果用doThrow()成功模拟了异常,而主代码把这个异常捕获了(比如空catch块),怎么确认这个异常确实被抛出过?
示例代码
业务代码
public void doFullOperation() { try { optionalService.blink(); } catch (IOException exception) { // 无任何处理逻辑 } }
测试代码(待补充验证逻辑)
doThrow(IOException.class).when(mockOptionalService).blink(); classUnderTest.doFullOperation(); // ???如何确认IOException确实被抛出过?
解决方案
1. 验证模拟方法的调用(间接确认)
因为doThrow()是绑定到模拟对象的方法调用上的,只要mockOptionalService.blink()被调用,就一定会抛出我们定义的IOException。所以通过验证方法的调用次数,就能间接确认异常被触发:
doThrow(IOException.class).when(mockOptionalService).blink(); classUnderTest.doFullOperation(); // 验证blink方法被调用过一次 verify(mockOptionalService, times(1)).blink();
逻辑:如果方法没被调用,异常不会抛出;一旦调用,必然触发异常,而主代码的catch块会捕获它,测试不会报错。
2. 自定义Answer记录异常触发状态(直接确认)
通过自定义Answer,在抛出异常的同时标记一个状态变量,事后验证这个变量来直接确认异常被抛出:
AtomicBoolean exceptionThrown = new AtomicBoolean(false); doAnswer(invocation -> { exceptionThrown.set(true); throw new IOException(); }).when(mockOptionalService).blink(); classUnderTest.doFullOperation(); // 断言异常确实被触发 assertTrue(exceptionThrown.get());
这种方式更直接,适合需要明确证明异常被抛出的场景。
3. 改造业务代码添加可观测行为(推荐)
如果允许修改业务代码,最好给catch块添加可观测的行为(比如日志),然后通过验证日志来确认异常被捕获:
修改后的业务代码
private static final Logger logger = LoggerFactory.getLogger(YourClass.class); public void doFullOperation() { try { optionalService.blink(); } catch (IOException exception) { logger.debug("捕获到IOException", exception); } }
对应的测试代码(以slf4j-test为例)
// 获取业务类的日志记录器 Logger logger = LoggerFactory.getLogger(YourClass.class); LoggingEventRecorder recorder = ((RecordingLogger) logger).getRecorder(); doThrow(IOException.class).when(mockOptionalService).blink(); classUnderTest.doFullOperation(); // 验证日志中存在对应的异常捕获记录 assertTrue(recorder.hasRecordMatching( level -> level == Level.DEBUG, msg -> msg.contains("捕获到IOException") ));
这种方式更贴合实际业务逻辑,同时能清晰验证异常的捕获流程。
内容的提问来源于stack exchange,提问作者Alexander
相关产品推荐
相关产品推荐

