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

如何用JUnit5+Mockito覆盖void方法的ExecutionException捕获块

问题原因

你的测试代码里虽然mock了LoadingCache并设置抛出ExecutionException,但这个mock对象并没有注入到测试的MyService(即你的sut)实例中。因为MyService的attemptsCache是在构造函数里直接初始化的,Mockito的@InjectMocks无法替换这个私有且无注入入口的字段,导致实际执行unlockFailed时用的是真实缓存实例,不会触发异常,自然走不到catch分支。

解决方案

要覆盖catch块,核心是让sut中的attemptsCache使用我们mock的对象,下面给出两种可行方案:

方案1:通过反射替换私有字段(无需修改原业务代码)

修改测试代码,在准备阶段用反射把mock的缓存注入到sut的私有字段中:

private final String USER = "fakeuser";
@InjectMocks
private MyService sut; // 注意要和你的业务类类名对应

@DisplayName("unlockFailed 应正确处理 ExecutionException")
@Test
public void unlockFailed_should_handle_ExecutionException() throws Exception {
    // Arrange
    LoadingCache<String, Integer> mockCache = Mockito.mock(LoadingCache.class);
    // 反射修改私有字段
    Field cacheField = MyService.class.getDeclaredField("attemptsCache");
    cacheField.setAccessible(true);
    cacheField.set(sut, mockCache);

    // 设置mock缓存的get方法抛出异常
    doThrow(new ExecutionException("Dummy ExecutionException", null))
            .when(mockCache).get(USER);

    // Act
    sut.unlockFailed(USER);

    // Assert
    // 验证catch块执行后,会调用put存入1(attempts初始0,catch后重置为0,自增后为1)
    Mockito.verify(mockCache, times(1)).get(USER);
    Mockito.verify(mockCache, times(1)).put(USER, 1);
}

方案2:修改业务类,提供缓存注入入口(测试友好写法)

如果允许修改原业务代码,可以新增带缓存参数的构造函数,方便测试时直接注入mock对象:

// 修改后的MyService
private LoadingCache<String, Integer> attemptsCache;

// 保留原构造函数,兼容现有逻辑
public MyService() {
    this(createDefaultCache());
}

// 新增构造函数,用于测试注入mock
public MyService(LoadingCache<String, Integer> attemptsCache) {
    super();
    this.attemptsCache = attemptsCache;
}

// 抽离默认缓存创建逻辑
private static LoadingCache<String, Integer> createDefaultCache() {
    return CacheBuilder.newBuilder()
            .expireAfterWrite(1, TimeUnit.HOURS)
            .build(new CacheLoader<String, Integer>() {
                @Override
                public Integer load(String key) throws Exception {
                    return 0;
                }
            });
}

// unlockFailed方法保持不变
public void unlockFailed(String key) {
    int attempts = 0;
    try {
        attempts = attemptsCache.get(key);
    } catch (ExecutionException e) {
        attempts = 0;
    }
    attempts++;
    attemptsCache.put(key, attempts);
}

对应的简化测试代码:

private final String USER = "fakeuser";

@DisplayName("unlockFailed 应正确处理 ExecutionException")
@Test
public void unlockFailed_should_handle_ExecutionException() throws ExecutionException {
    // Arrange
    LoadingCache<String, Integer> mockCache = Mockito.mock(LoadingCache.class);
    MyService sut = new MyService(mockCache); // 直接传入mock缓存

    doThrow(new ExecutionException("Dummy ExecutionException", null))
            .when(mockCache).get(USER);

    // Act
    sut.unlockFailed(USER);

    // Assert
    Mockito.verify(mockCache).get(USER);
    Mockito.verify(mockCache).put(USER, 1);
}
关键验证逻辑

两种方案的核心断言点一致:

  • 确认attemptsCache.get(key)被调用一次(触发异常的前提)
  • 确认attemptsCache.put(key, 1)被调用一次(证明catch块执行后,逻辑正常走完)

这样就能确保catch分支被实际执行,覆盖对应的代码块。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 16:25:28