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个:
- Mock对象未生效:测试代码中手动Mock的
ObjectMapper实例,和被测类里自己初始化的私有mapper成员完全是两个独立对象。被测代码执行时调用的是自己持有的真实ObjectMapper实例,根本不会用到你在测试里Mock的那个对象,自然不会触发你设置的抛异常规则。 - Stub写法存在隐患:使用
Mockito.when(mapper.writeValueAsString(xxx))写法时,括号内的方法会被真实执行一次,且参数默认是严格相等匹配,就算Mock对象注入正确,只要传入的参数实例和Stub中使用的实例不匹配,也不会触发抛异常逻辑。 - 异常实例化问题:
JsonProcessingException是受检异常,没有公开的无参构造函数,直接传入.class让Mockito自行实例化很容易失败,导致Stub规则失效。
修正方案
按以下步骤调整即可让异常按预期抛出,正确覆盖异常分支:
- 注入Mock的ObjectMapper:把被测类中持有的真实
ObjectMapper替换成测试中Mock的实例。可以选择构造函数注入、Setter注入,或者用反射工具(如ReflectionTestUtils)直接注入私有成员,也可以直接用Mockito的@Mock+@InjectMocks注解自动完成注入。 - 调整Stub写法:用
doThrow()替代when().thenThrow()的写法,避免提前执行Mock方法的副作用;参数匹配用any(Class)匹配器替代硬编码的实例传参,避免参数不匹配导致Stub失效。 - 抛出可正常实例化的异常:不要直接传异常类让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
相关产品推荐
相关产品推荐

