如何使用Spring Boot与Mockito模拟JSON文档完成方法测试
Spring Boot结合Mockito测试JSON列表入参处理方法的实操方案
测试核心思路
这类方法的测试不需要启动完整Spring容器,也不用Mock HTTP请求,核心覆盖三个逻辑节点即可:入参JSON和Java实体的映射正确性、业务处理逻辑的分支覆盖、返回结果符合预期。Mockito的作用是隔离数据库、远程调用这类外部依赖,JSON测试文档不需要做Mock,直接构造真实的结构化字符串即可,绝对不要Mock JSON解析器的核心方法,否则等于跳过解析逻辑白测。
具体操作步骤
1. 测试类基础配置
用JUnit5+Mockito做纯单元测试即可,测试类加@ExtendWith(MockitoExtension.class)注解,不需要加载整个Spring上下文,跑测速度快。如果是测Controller层的接口入参,再加@WebMvcTest做切片测试就行。
先举个待测试的业务方法示例,方便对照:
// 待测试方法:接收集合格式的JSON字符串,解析后处理数据,返回VO列表 @Service public class UserService { @Autowired private UserRepository userRepository; // 数据库操作依赖,测试时需要Mock @Autowired private ObjectMapper objectMapper; // 框架自带JSON解析器,不用Mock public List<UserVO> batchImportUsers(String jsonDoc) throws JsonProcessingException { // 第一步:解析JSON为DTO列表 List<UserCreateDTO> dtoList = objectMapper.readValue(jsonDoc, new TypeReference<>() {}); // 第二步:字段校验 validateUserDtoList(dtoList); // 第三步:转换为实体存库 List<User> entityList = dtoList.stream().map(User::new).toList(); List<User> savedUsers = userRepository.saveAll(entityList); // 第四步:转换为VO返回 return savedUsers.stream().map(UserVO::new).toList(); } }
2. 构造测试用JSON文档
两种靠谱的构造方式,根据JSON复杂度选:
- 短JSON直接用Java文本块写常量,覆盖正常、异常、边界三类场景
- 结构复杂的长JSON,存在
src/test/resources/testdata目录下的json文件里,测试时读成字符串就行,比硬编码好维护
示例测试JSON常量:
// 正常场景:2个结构合法的用户对象 private static final String VALID_BATCH_JSON = """ [ {"userId":1,"userName":"zhangsan","age":25,"email":"zhangsan@corp.com"}, {"userId":2,"userName":"lisi","age":30,"email":"lisi@corp.com"} ] """; // 异常场景:第二个对象缺必填的userName字段 private static final String MISSING_REQUIRED_FIELD_JSON = """ [ {"userId":1,"userName":"zhangsan","age":25,"email":"zhangsan@corp.com"}, {"userId":2,"age":30,"email":"lisi@corp.com"} ] """; // 边界场景:空数组 private static final String EMPTY_ARRAY_JSON = "[]"; // 异常场景:非法JSON格式 private static final String INVALID_JSON_STR = "[{\"userId\":1,userName:'zhangsan'}]";
3. 用Mockito隔离外部依赖
只Mock和业务逻辑无关的外部IO依赖,核心的解析、计算、校验逻辑不要Mock:
- 用
@Mock标记数据库操作、远程调用这类外部依赖组件 - 用
@InjectMocks标记待测试的服务类,Mockito会自动把Mock的依赖注入进去 - 对Mock组件做行为打桩,比如存库操作直接返回预设的实体,不要真的连库
示例正常场景测试代码:
@ExtendWith(MockitoExtension.class) class UserServiceTest { @InjectMocks private UserService userService; @Mock private UserRepository userRepository; // 用和生产环境一致的ObjectMapper配置 private final ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build(); @Test void batchImportUsers_validJson_returnCorrectVoList() throws JsonProcessingException { // 打桩:存库操作直接返回构造好的实体,不真的访问数据库 List<User> mockSavedEntities = objectMapper.readValue(VALID_BATCH_JSON, new TypeReference<>() {}); when(userRepository.saveAll(anyList())).thenReturn(mockSavedEntities); // 执行待测试方法 List<UserVO> result = userService.batchImportUsers(VALID_BATCH_JSON); // 结果断言 assertEquals(2, result.size()); assertEquals("zhangsan", result.get(0).getUserName()); assertEquals("lisi", result.get(1).getUserName()); // 行为验证:确认存库方法被正确调用,参数长度符合预期 verify(userRepository, times(1)).saveAll(argThat(list -> list.size() == 2)); } }
4. 覆盖异常与边界场景
不要只测正常流程,要把各环节的异常分支都覆盖到:
@Test void batchImportUsers_missingRequiredField_throwValidationException() { // 断言缺字段时抛出预期的校验异常 assertThrows(ParamCheckException.class, () -> userService.batchImportUsers(MISSING_REQUIRED_FIELD_JSON)); // 校验不通过时存库方法不应该被调用 verify(userRepository, never()).saveAll(anyList()); } @Test void batchImportUsers_emptyArray_returnEmptyList() throws JsonProcessingException { List<UserVO> result = userService.batchImportUsers(EMPTY_ARRAY_JSON); assertTrue(result.isEmpty()); verify(userRepository, times(1)).saveAll(argThat(List::isEmpty)); } @Test void batchImportUsers_invalidJson_throwParseException() { assertThrows(JsonProcessingException.class, () -> userService.batchImportUsers(INVALID_JSON_STR)); verify(userRepository, never()).saveAll(anyList()); }
常见踩坑
- 别为了追求“单元测试要全Mock”把ObjectMapper、字段校验器这类核心组件也Mock掉,这么做会跳过JSON字段映射、类型转换这类容易出问题的逻辑,测试完全没有参考价值
- 如果是测Controller层的JSON入参,用
@WebMvcTest+MockMvc的时候,直接把构造好的JSON字符串当requestBody传入即可,只需要Mock Service层的返回,就能同时验证参数解析、响应序列化的逻辑 - 测试用的JSON结构要和生产环境真实入参保持一致,不要随便简化字段、改字段名,否则上线才发现字段名不匹配、类型不兼容就会出线上故障
内容的提问来源于stack exchange,提问作者Elmo Pro
相关产品推荐
相关产品推荐

