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

Mockito重复调用when()抛出异常的问题排查与解决

问题描述

我有一个可发起API调用的Api类,接收Long和Integer类型参数。Controller类中调用该API并处理潜在错误的核心代码如下:

public String save() {
    try {
        String rc = api.doApiCall(l, i);
        if(rc.equals("200"))
            return "nextpage.html";
    } catch (RuntimeException ignored) {

    }
    return "error.html";
}

使用Mockito模拟Api类编写的Controller测试用例如下:

@BeforeEach
void setUp() {
    controller = new Controller();
    api = mock(Api.class);
    controller.api = api;
}
    
@Test
void testApiCall() {
    when(api.doApiCall(any(Long.class), any(Integer.class)))
        .thenReturn("200");
    
    assertEquals("nextpage.html", controller.save());
        
    when(api.doApiCall(any(Long.class), any(Integer.class)))
        .thenThrow(new RuntimeException("400"));
        
    assertEquals("error.html", controller.save());
        
    when(api.doApiCall(any(Long.class), any(Integer.class)))
        .thenThrow(new RuntimeException("401"));
    assertEquals("error.html", controller.save());
}

执行测试时,第三个when()调用会抛出第二个when()中指定的异常,导致测试失败。尝试将第三个when()中的any(Integer.class)替换为any()后测试正常,但新增如下测试用例时仍出现相同问题:

when(api.doApiCall(any(Long.class), any()))
    .thenThrow(new RuntimeException("402"));
assertEquals("error.html", controller.save());
问题原因

问题出在Mockito的when()语法执行逻辑上:使用when(mock.method(...))时,会先执行一次mock对象的方法调用,再将后续的thenReturn/thenThrow关联到这个调用上。

当你在第二个存根中设置了thenThrow(new RuntimeException("400"))后,再次调用when(api.doApiCall(any(Long.class), any(Integer.class)))时,这个方法调用会触发之前的存根逻辑,直接抛出RuntimeException("400"),导致无法完成新的存根设置,进而测试失败。

解决方法

方法一:改用doThrow/doReturn系列语法

这类语法是先指定行为,再绑定到目标方法,不会提前执行mock的方法调用,避免触发之前的存根异常。修改后的测试用例如下:

@Test
void testApiCall() {
    // 存根成功返回场景
    doReturn("200").when(api).doApiCall(any(Long.class), any(Integer.class));
    assertEquals("nextpage.html", controller.save());
        
    // 存根抛出400异常场景
    doThrow(new RuntimeException("400")).when(api).doApiCall(any(Long.class), any(Integer.class));
    assertEquals("error.html", controller.save());
        
    // 存根抛出401异常场景
    doThrow(new RuntimeException("401")).when(api).doApiCall(any(Long.class), any(Integer.class));
    assertEquals("error.html", controller.save());

    // 存根抛出402异常场景
    doThrow(new RuntimeException("402")).when(api).doApiCall(any(Long.class), any());
    assertEquals("error.html", controller.save());
}

方法二:拆分测试场景为独立方法

将不同的测试场景拆分为独立的测试方法,每个测试方法单独存根,避免在同一个测试方法中多次修改存根,同时让测试逻辑更清晰:

@Test
void testApiCallSuccess() {
    doReturn("200").when(api).doApiCall(any(Long.class), any(Integer.class));
    assertEquals("nextpage.html", controller.save());
}

@Test
void testApiCallThrow400() {
    doThrow(new RuntimeException("400")).when(api).doApiCall(any(Long.class), any(Integer.class));
    assertEquals("error.html", controller.save());
}

@Test
void testApiCallThrow401() {
    doThrow(new RuntimeException("401")).when(api).doApiCall(any(Long.class), any(Integer.class));
    assertEquals("error.html", controller.save());
}

@Test
void testApiCallThrow402() {
    doThrow(new RuntimeException("402")).when(api).doApiCall(any(Long.class), any());
    assertEquals("error.html", controller.save());
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 04:30:30