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

Mockito单元测试预期JsonProcessingException未抛出问题

问题描述

单元测试持续运行失败:预期测试执行过程中抛出指定异常,但实际异常始终未被触发。
测试场景分为两类:

  • 正常流程(happy path):日期对象经过格式化后返回
  • 当前测试用例覆盖异常分支:抛出异常时日期不做额外格式化处理,但实际测试时异常始终未抛出,无法进入预期的catch分支。
相关代码

待修复的单元测试代码

public void testGetMyObjectJPE() throws JsonProcessingException {
    MyObject myObject = Mockito.spy(generateMyObject());
    ObjectMapper mapper = Mockito.mock(ObjectMapper.class);
    Date testCreateDate = new Date();

    Mockito.when(myObject.getCreateTime()).thenReturn(testCreateDate);
    Mockito.when(mapper.writeValueAsString(myObject.getCreateTime())).thenThrow(JsonProcessingException.class);
    Mockito.when(myObject.getCreateTime()).thenReturn(testCreateDate);

    MySecObject c = myMapper.getMyObject(myObject, null);
    assertEquals(testCreateDate.toString(), c.getCreateTime());
}

被测业务代码

private ObjectMapper mapper = new ObjectMapper();

public MySecObj getMyObject(@NotNull MyObject myObject,
        final MyThirdObj myThirdObj) {
    mapper.setDateFormat(new ISO8601DateFormat());
    MySecObj c = new MySecObj();
    c.setId(mySecObj.getId());
    
    // timestamps
    if (mysecObj.getCreateTime() != null) {
        try {
            c.setCreateTime(mapper.writeValueAsString(mySecObj.getCreateTime()).replaceAll("\"", ""));
        } catch (JsonProcessingException jpe) {
            Logger.warn("JSON processing exception for create time " + mySecObj.getCreateTime());
            c.setCreateTime(mySecObj.getCreateTime().toString());
        }
    }

    mapper(c, mySecObj, myThirdObj);
    return c;
}
根本原因

异常未触发的核心原因有3个:

  1. Mock对象未生效:测试代码中手动Mock的ObjectMapper实例,和被测类里自己初始化的私有mapper成员完全是两个独立对象。被测代码执行时调用的是自己持有的真实ObjectMapper实例,根本不会用到你在测试里Mock的那个对象,自然不会触发你设置的抛异常规则。
  2. Stub写法存在隐患:使用Mockito.when(mapper.writeValueAsString(xxx))写法时,括号内的方法会被真实执行一次,且参数默认是严格相等匹配,就算Mock对象注入正确,只要传入的参数实例和Stub中使用的实例不匹配,也不会触发抛异常逻辑。
  3. 异常实例化问题:JsonProcessingException是受检异常,没有公开的无参构造函数,直接传入.class让Mockito自行实例化很容易失败,导致Stub规则失效。
修正方案

按以下步骤调整即可让异常按预期抛出,正确覆盖异常分支:

  1. 注入Mock的ObjectMapper:把被测类中持有的真实ObjectMapper替换成测试中Mock的实例。可以选择构造函数注入、Setter注入,或者用反射工具(如ReflectionTestUtils)直接注入私有成员,也可以直接用Mockito的@Mock+@InjectMocks注解自动完成注入。
  2. 调整Stub写法:用doThrow()替代when().thenThrow()的写法,避免提前执行Mock方法的副作用;参数匹配用any(Class)匹配器替代硬编码的实例传参,避免参数不匹配导致Stub失效。
  3. 抛出可正常实例化的异常:不要直接传异常类让Mockito实例化,手动创建一个JsonProcessingException的具体子类实例(如JsonParseException)传入,保证异常能正常抛出。

修正后的测试代码参考

@RunWith(MockitoJUnitRunner.class)
public class MyMapperTest {
    // Mock的ObjectMapper实例,会自动注入到被测类中
    @Mock
    private ObjectMapper objectMapper;
    // 被测类实例,自动注入@Mock标注的依赖
    @InjectMocks
    private MyMapper myMapper;

    @Test
    public void testGetMyObjectJPE() throws JsonProcessingException {
        MyObject myObject = spy(generateMyObject());
        Date testCreateDate = new Date();
        when(myObject.getCreateTime()).thenReturn(testCreateDate);
        
        // 配置Mock规则:调用writeValueAsString传任意Date类型参数时抛出异常
        doThrow(new JsonParseException(null, "mock json process error"))
                .when(objectMapper)
                .writeValueAsString(any(Date.class));

        MySecObject result = myMapper.getMyObject(myObject, null);
        assertEquals(testCreateDate.toString(), result.getCreateTime());
    }
}

注:如果被测代码中存在变量名笔误(比如入参名为myObject,方法内却调用mysecObj.getCreateTime()),需要先修正业务代码的变量名错误,否则测试本身就会因为编译/运行报错无法执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 06:21:47