You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JUnit 5 assertThrows()无法捕获方法内部try-catch处理的异常,如何验证该异常已被抛出?

这个问题我之前踩过坑!当方法内部把异常捕获处理了,assertThrows确实抓不到,但咱们有几种可行的办法来验证异常确实被抛出过,分情况给你说说:

方案1:重构目标代码(最推荐,长期维护友好)

如果能修改SendEmail类的代码,这是最优解,调整后能让测试逻辑更清晰:

  • 抽离独立方法单独测试:从栈跟踪能看到异常是htmlToString方法抛出来的,要是把这个方法改成public(或者测试可见),直接测试它就行,assertThrows就能正常捕获异常了:
@Test
void testHtmlToStringThrowsFileNotFound() {
    assertThrows(FileNotFoundException.class, () -> {
        SendEmail.htmlToString("fakeLocation");
    });
}
  • 让方法返回异常状态:修改sendMail方法,让它返回一个包含异常信息的结果(比如Optional<Exception>),测试时就能直接检查这个结果:
// 修改后的SendEmail.sendMail方法
public static Optional<Exception> sendMail(String to, String subject, String filePath) {
    try {
        String htmlContent = htmlToString(filePath);
        // 原邮件发送逻辑
        return Optional.empty();
    } catch (FileNotFoundException e) {
        // 原异常处理逻辑(比如打日志)
        return Optional.of(e);
    }
}

// 对应的测试代码
@Test
void testEmailSentHandlesFileNotFound() {
    Optional<Exception> result = SendEmail.sendMail(tenminuteMail, "testing", "fakeLocation");
    
    assertTrue(result.isPresent());
    assertTrue(result.get() instanceof FileNotFoundException);
    assertEquals("fakeLocation (The system cannot find the file specified)", result.get().getMessage());
}
  • 提供测试专用的重载方法:如果业务不允许修改原有方法的行为,可以加一个重载方法,在测试场景下不捕获异常,直接抛出。

方案2:模拟依赖(适合异常来自外部资源的情况)

如果异常是因为文件读取这类外部操作导致的,你可以把文件读取逻辑封装成一个独立的服务类,然后用Mockito这类框架模拟这个服务的行为,验证它是否抛出了预期异常:

// 重构后的SendEmail类,依赖文件读取服务
public class SendEmail {
    private final FileContentReader fileReader;

    // 构造注入,方便测试时替换为mock
    public SendEmail(FileContentReader fileReader) {
        this.fileReader = fileReader;
    }

    public void sendMail(String to, String subject, String filePath) {
        try {
            String html = fileReader.readToString(filePath);
            // 邮件发送逻辑
        } catch (FileNotFoundException e) {
            // 异常处理逻辑
            log.error("Failed to read email template file: {}", filePath, e);
        }
    }
}

// 测试代码
@Test
void testSendMailTriggersFileNotFound() {
    // 创建mock的文件读取服务
    FileContentReader mockReader = Mockito.mock(FileContentReader.class);
    // 配置当传入fakeLocation时抛出异常
    Mockito.when(mockReader.readToString("fakeLocation"))
           .thenThrow(new FileNotFoundException("fakeLocation"));

    SendEmail emailSender = new SendEmail(mockReader);
    emailSender.sendMail(tenminuteMail, "testing", "fakeLocation");

    // 验证mock方法确实被调用过(说明异常触发了)
    Mockito.verify(mockReader).readToString("fakeLocation");
}

方案3:检查日志输出(无需改代码的折中方案)

如果你的SendEmail在捕获异常后会打印日志(比如用SLF4J、Log4j),可以用日志测试工具来验证日志中是否包含了目标异常的信息。比如用Logback的MemoryAppender:

@Test
void testSendMailLogsFileNotFound() {
    // 配置MemoryAppender来捕获SendEmail类的日志
    MemoryAppender logAppender = new MemoryAppender();
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    Logger targetLogger = loggerContext.getLogger(SendEmail.class);
    
    logAppender.setContext(loggerContext);
    targetLogger.addAppender(logAppender);
    logAppender.start();

    // 执行目标方法
    SendEmail.sendMail(tenminuteMail, "testing", "fakeLocation");

    // 验证日志中存在预期的异常信息
    assertTrue(logAppender.contains("FileNotFoundException", Level.ERROR));
    assertTrue(logAppender.contains("fakeLocation", Level.ERROR));
}

方案4:用PowerMock(仅当完全无法修改目标代码时)

如果完全不能改SendEmail的代码,这是最后的无奈之举——用PowerMock拦截底层的异常抛出逻辑。不过这种方式侵入性强,测试容易受代码变更影响,不推荐长期使用:

@RunWith(PowerMockRunner.class)
@PrepareForTest(SendEmail.class)
public class SendEmailTestClass {
    @Test
    void testEmailSentTriggersFileNotFound() throws Exception {
        // 拦截FileReader的构造方法,让它抛出异常
        PowerMock.expectNew(FileReader.class, "fakeLocation")
                 .andThrow(new FileNotFoundException("fakeLocation"));
        PowerMock.replay(FileReader.class);

        // 执行目标方法
        SendEmail.sendMail(tenminuteMail, "testing", "fakeLocation");

        // 验证异常确实被触发过
        PowerMock.verify(FileReader.class);
    }
}

总的来说,优先选方案1或2,它们能让代码更具可测试性,也符合良好的代码设计原则;方案3适合快速验证且不想改代码的场景;方案4尽量少用,除非万不得已。

内容的提问来源于stack exchange,提问作者Ryan Reddy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 01:57:29