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

Mockito异常:抛出正确IOException仍提示Checked exception无效的问题

解决Mockito模拟ObjectMapper.readValue抛出IOException时的异常问题

问题描述

我在测试DecileConfigurationJsonConverter类的数据库数据读取异常场景时遇到了问题:

  • 该类实现了AttributeConverter<List<DecileParameter>, String>接口,convertToEntityAttribute方法会捕获IOException并返回null;
  • 用Mockito模拟ObjectMapper的readValue方法抛出java.io.IOException时,触发org.mockito.exceptions.base.MockitoException,提示“Checked exception is invalid for this method!”。

相关代码

转换器类

public class DecileConfigurationJsonConverter implements AttributeConverter<List<DecileParameter>, String> {
    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public List<DecileParameter> convertToEntityAttribute(String dbData) {
        if (dbData == null)
            return null;
        List<DecileParameter> dep = null;
        try {
            dep = objectMapper.readValue(dbData,
                    new TypeReference<List<DecileParameter>>() {});
        } catch (final IOException e) {
            return null;
        }
        return dep;
    }

    // 此处省略convertToDatabaseColumn方法实现
}

测试类

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ActiveProfiles("dev-test")
@TestPropertySource(locations = "classpath:application-dev-test.yml")
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class UtilPackageTestMore {

    @Mock
    ObjectMapper objectMapper;

    private static void setFinalStaticField(Class<?> clazz, String fieldName, Object value)
            throws ReflectiveOperationException {

        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);

        Field modifiers = Field.class.getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

        field.set(null, value);
    }

    @Test
    public void TestDecileConfigurationJsonConverterExceptions() throws Exception {
        DecileConfigurationJsonConverter dc = new DecileConfigurationJsonConverter();
        ObjectMapper objecmapper = Mockito.mock(ObjectMapper.class);
        setFinalStaticField(DecileConfigurationJsonConverter.class, "objectMapper", objecmapper);

        List<DecileParameter> attribute = new ArrayList<>();
        attribute.add(new DecileParameter("1","2","3","4","5"));
        Mockito.when(objecmapper.writeValueAsString(attribute)).thenThrow(JsonProcessingException.class);
        String res = dc.convertToDatabaseColumn(attribute);
        assertNull(res);

        // 此处触发异常
        Mockito.when(objecmapper.readValue("db data",new TypeReference<List<DecileParameter>>())).thenThrow(java.io.IOException.class);
        List<DecileParameter> convertToEntityAttribute = dc.convertToEntityAttribute("db data");
        assertNull(convertToEntityAttribute);
    }
}

问题原因

Mockito禁止模拟方法抛出该方法声明中未定义的检查型异常。查看ObjectMapper.readValue的方法签名,它实际声明抛出的是JsonProcessingException(IOException的子类),而非直接的IOException,因此直接抛出IOException会被判定为无效操作。

解决方法

1. 使用正确的异常类型模拟

将抛出的异常改为JsonProcessingException(或其子类如JsonParseException),这是readValue方法允许抛出的检查型异常:

// 修改测试中的异常抛出代码
Mockito.when(objecmapper.readValue("db data", new TypeReference<List<DecileParameter>>())).thenThrow(JsonProcessingException.class);
List<DecileParameter> convertToEntityAttribute = dc.convertToEntityAttribute("db data");
assertNull(convertToEntityAttribute);

如果需要更贴合真实解析失败场景,也可以使用具体的异常子类:

Mockito.when(objecmapper.readValue("db data", new TypeReference<List<DecileParameter>>()))
       .thenThrow(new JsonParseException("JSON解析失败", null));

2. 优化转换器类的可测试性(推荐)

通过构造函数注入ObjectMapper,避免使用反射修改静态final字段的繁琐操作,让测试更优雅:

public class DecileConfigurationJsonConverter implements AttributeConverter<List<DecileParameter>, String> {
    private final ObjectMapper objectMapper;

    // 构造函数注入,供测试使用
    public DecileConfigurationJsonConverter(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    // 无参构造函数,兼容Spring自动装配
    public DecileConfigurationJsonConverter() {
        this(new ObjectMapper());
    }

    @Override
    public List<DecileParameter> convertToEntityAttribute(String dbData) {
        if (dbData == null)
            return null;
        try {
            return objectMapper.readValue(dbData, new TypeReference<List<DecileParameter>>() {});
        } catch (final IOException e) {
            return null;
        }
    }

    // 实现convertToDatabaseColumn方法...
}

修改后的测试代码无需反射,直接传入Mock对象:

@Test
public void TestDecileConfigurationJsonConverterExceptions() throws Exception {
    ObjectMapper mockMapper = Mockito.mock(ObjectMapper.class);
    DecileConfigurationJsonConverter dc = new DecileConfigurationJsonConverter(mockMapper);

    List<DecileParameter> attribute = new ArrayList<>();
    attribute.add(new DecileParameter("1","2","3","4","5"));
    Mockito.when(mockMapper.writeValueAsString(attribute)).thenThrow(JsonProcessingException.class);
    String res = dc.convertToDatabaseColumn(attribute);
    assertNull(res);

    Mockito.when(mockMapper.readValue("db data", new TypeReference<List<DecileParameter>>())).thenThrow(JsonProcessingException.class);
    List<DecileParameter> convertToEntityAttribute = dc.convertToEntityAttribute("db data");
    assertNull(convertToEntityAttribute);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 09:02:53