Java单元测试技巧之JSON序列化
一 前言
1 冗长的单元测试代码
冗长的数据模拟代码
1)模拟类属性值
Map<Long, String> languageMap = new HashMap<>(MapHelper.DEFAULT);languageMap.put(1L, 'Java');languageMap.put(2L, 'C++');languageMap.put(3L, 'Python');languageMap.put(4L, 'JavaScript');... // 约几十行Whitebox.setInternalState(developmentService, 'languageMap', languageMap);2)模拟方法参数值
List<UserCreateVO> userCreateList = new ArrayList<>();UserCreateVO userCreate0 = new UserCreateVO();userCreate0.setName('Changyi');userCreate0.setTitle('Java Developer');... // 约几十行userCreateList.add(userCreate0);UserCreateVO userCreate1 = new UserCreateVO();userCreate1.setName('Tester');userCreate1.setTitle('Java Tester');... // 约几十行userCreateList.add(userCreate1);... // 约几十条userService.batchCreate(userCreateList);3)模拟方法返回值
Long companyId = 1L;List<UserDO> userList = new ArrayList<>();UserDO user0 = new UserDO();user0.setId(1L);user0.setName('Changyi');user0.setTitle('Java Developer');... // 约几十行userList.add(user0);UserDO user1 = new UserDO();user1.setId(2L);user1.setName('Tester');user1.setTitle('Java Tester');... // 约几十行userList.add(user1);... // 约几十条Mockito.doReturn(userList).when(userDAO).queryByCompanyId(companyId);冗长的数据验证代码
1)验证方法返回值
Long companyId = 1L;List<UserVO> userList = userService.queryByCompanyId(companyId);UserVO user0 = userList.get(0);Assert.assertEquals('name不一致', 'Changyi', user0.getName());Assert.assertEquals('title不一致', 'Java Developer', user0.getTitle());... // 约几十行UserVO user1 = userList.get(1);Assert.assertEquals('name不一致', 'Tester', user1.getName());Assert.assertEquals('title不一致', 'Java Tester', user1.getTitle());... // 约几十行... // 约几十条2)验证方法参数值
ArgumentCaptor<List<UserDO>> userCreateListCaptor = CastUtils.cast(ArgumentCaptor.forClass(List.class));Mockito.verify(userDAO).batchCreate(userCreateListCaptor.capture());List<UserDO> userCreateList = userCreateListCaptor.getValue();UserDO userCreate0 = userCreateList.get(0);Assert.assertEquals('name不一致', 'Changyi', userCreate0.getName());Assert.assertEquals('title不一致', 'Java Developer', userCreate0.getTitle());... // 约几十行UserDO userCreate1 = userCreateList.get(1);Assert.assertEquals('name不一致', 'Tester', userCreate1.getName());Assert.assertEquals('title不一致', 'Java Tester', userCreate1.getTitle());... // 约几十行... // 约几十条2 采用JSON序列化简化
简化数据模拟代码
1)模拟类属性值
String text = ResourceHelper.getResourceAsString(getClass(), path + 'languageMap.json');Map<Long, String> languageMap = JSON.parseObject(text, new TypeReference<Map<Long, String>>() {});Whitebox.setInternalState(mobilePhoneService, 'languageMap', languageMap);{1:'Java',2:'C++',3:'Python',4:'JavaScript'...}2)模拟方法参数值
String text = ResourceHelper.getResourceAsString(getClass(), path + 'userCreateList.json');List<UserCreateVO> userCreateList = JSON.parseArray(text, UserCreateVO.class);userService.batchCreate(userCreateList);[{'name':'Changyi','title':'Java Developer'...},{'name':'Tester','title':'Java Tester'...},...]3)模拟方法返回值
Long companyId = 1L;String text = ResourceHelper.getResourceAsString(getClass(), path + 'userList.json');List<UserDO> userList = JSON.parseArray(text, UserDO.class);Mockito.doReturn(userList).when(userDAO).queryByCompanyId(companyId);[{'id':1,'name':'Changyi','title':'Java Developer'...},{'id':2,'name':'Tester','title':'Java Tester'...},...]简化数据验证代码
1)验证方法返回值
Long companyId = 1L;List<UserVO> userList = userService.queryByCompanyId(companyId);String text = ResourceHelper.getResourceAsString(getClass(), path + 'userList.json');Assert.assertEquals('用户列表不一致', text, JSON.toJSONString(userList));[{'name':'Changyi','title':'Java Developer'...},{'name':'Tester','title':'Java Tester'...},...]2)验证方法参数值
ArgumentCaptor<List<UserDO>> userCreateListCaptor = CastUtils.cast(ArgumentCaptor.forClass(List.class));Mockito.verify(userDAO).batchCreate(userCreateListCaptor.capture());String text = ResourceHelper.getResourceAsString(getClass(), path + 'userCreateList.json');Assert.assertEquals('用户创建列表不一致', text, JSON.toJSONString(userCreateListCaptor.getValue()));[{'name':'Changyi','title':'Java Developer'...},{'name':'Tester','title':'Java Tester'...},...]3 测试用例及资源命名
测试类命名
测试方法命名
按照结果命名: testBatchCreateWithSuccess(测试:批量创建-成功); testBatchCreateWithFailure(测试:批量创建-失败); testBatchCreateWithException(测试:批量创建-异常); 按照参数命名: testBatchCreateWithListNull(测试:批量创建-列表为NULL); testBatchCreateWithListEmpty(测试:批量创建-列表为空); testBatchCreateWithListNotEmpty(测试:批量创建-列表不为空); 按照意图命名: testBatchCreateWithNormal(测试:批量创建-正常); testBatchCreateWithGray(测试:批量创建-灰度); testBatchCreateWithException(测试:批量创建-异常);
测试类资源目录命名
放在“src/test/java”目录下,跟测试类放在同一目录下——这是作者最喜欢的方式; 放在“src/test/resources”目录下,跟测试类放在同一目录下——建议IDEA用户采用这种方式。
测试方法资源目录命名
测试资源文件命名
String text = ResourceHelper.getResourceAsString(getClass(), path + 'userCreateList.json');List<UserCreateVO> userCreateList = JSON.parseArray(text, UserCreateVO.class);userService.batchCreate(userCreateList);测试资源文件存储
如果是测试类下所有测试用例共用的资源文件,建议存储在测试类资源目录下,比如:testUserService; 如果是测试用例独有的资源文件,建议存储在测试方法资源目录下,比如:testUserService/testBatchCreateWithSuccess; 如果是某一被测方法所有的测试用例共用的资源文件,建议存储在不带任何修饰的测试方法资源目录下,比如:testUserService/testBatchCreate; 如果测试类资源目录下只有一个测试方法资源目录,可以去掉这个测试方法资源目录,把所有资源文件存储在测试类资源目录下。
Git文件名称过长
git checkout developerror: xxx/xxx: Filename too longgit add .error: open('xxx/xxx'): Filename too longerror: unable to index file 'xxx/xxx'fatal: adding files failedgit config --system core.longpaths trueJSON资源文件格式
4 测试资源使用案例
被测案例代码
/** * 用户服务类 */@Servicepublic class UserService {
/** 服务相关 */ /** 用户DAO */ @Autowired private UserDAO userDAO; /** 标识生成器 */ @Autowired private IdGenerator idGenerator;
/** 参数相关 */ /** 可以修改 */ @Value('${userService.canModify}') private Boolean canModify;
/** * 创建用户 * * @param userCreate 用户创建 * @return 用户标识 */ public Long createUser(UserVO userCreate) { // 获取用户标识 Long userId = userDAO.getIdByName(userCreate.getName());
// 根据存在处理 // 根据存在处理: 不存在则创建 if (Objects.isNull(userId)) { userId = idGenerator.next(); UserDO userCreateDO = new UserDO(); userCreateDO.setId(userId); userCreateDO.setName(userCreate.getName()); userDAO.create(userCreateDO); } // 根据存在处理: 已存在可修改 else if (Boolean.TRUE.equals(canModify)) { UserDO userModifyDO = new UserDO(); userModifyDO.setId(userId); userModifyDO.setName(userCreate.getName()); userDAO.modify(userModifyDO); } // 根据存在处理: 已存在禁修改 else { throw new UnsupportedOperationException('不支持修改'); }
// 返回用户标识 return userId; }
}测试用例代码
/** * 用户服务测试类 */@RunWith(PowerMockRunner.class)public class UserServiceTest {
/** 模拟依赖对象 */ /** 用户DAO */ @Mock private UserDAO userDAO; /** 标识生成器 */ @Mock private IdGenerator idGenerator;
/** 定义测试对象 */ /** 用户服务 */ @InjectMocks private UserService userService;
/** 定义静态常量 */ /** 资源路径 */ private static final String RESOURCE_PATH = 'testUserService/';
/** * 在测试之前 */ @Before public void beforeTest() { // 注入依赖对象 Whitebox.setInternalState(userService, 'canModify', Boolean.TRUE); }
/** * 测试: 创建用户-创建 */ @Test public void testCreateUserWithCreate() { // 模拟依赖方法 // 模拟依赖方法: userDAO.getByName Mockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString()); // 模拟依赖方法: idGenerator.next Long userId = 1L; Mockito.doReturn(userId).when(idGenerator).next();
// 调用测试方法 String path = RESOURCE_PATH + 'testCreateUserWithCreate/'; String text = ResourceHelper.getResourceAsString(getClass(), path + 'userCreateVO.json'); UserVO userCreate = JSON.parseObject(text, UserVO.class); Assert.assertEquals('用户标识不一致', userId, userService.createUser(userCreate));
// 验证依赖方法 // 验证依赖方法: userDAO.getByName Mockito.verify(userDAO).getIdByName(userCreate.getName()); // 验证依赖方法: idGenerator.next Mockito.verify(idGenerator).next(); // 验证依赖方法: userDAO.create ArgumentCaptor<UserDO> userCreateCaptor = ArgumentCaptor.forClass(UserDO.class); Mockito.verify(userDAO).create(userCreateCaptor.capture()); text = ResourceHelper.getResourceAsString(getClass(), path + 'userCreateDO.json'); Assert.assertEquals('用户创建不一致', text, JSON.toJSONString(userCreateCaptor.getValue()));
// 验证依赖对象 // 验证依赖对象: idGenerator, userDAO Mockito.verifyNoMoreInteractions(idGenerator, userDAO); }
/** * 测试: 创建用户-修改 */ @Test public void testCreateUserWithModify() { // 模拟依赖方法 // 模拟依赖方法: userDAO.getByName Long userId = 1L; Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());
// 调用测试方法 String path = RESOURCE_PATH + 'testCreateUserWithModify/'; String text = ResourceHelper.getResourceAsString(getClass(), path + 'userCreateVO.json'); UserVO userCreate = JSON.parseObject(text, UserVO.class); Assert.assertEquals('用户标识不一致', userId, userService.createUser(userCreate));
// 验证依赖方法 // 验证依赖方法: userDAO.getByName Mockito.verify(userDAO).getIdByName(userCreate.getName()); // 验证依赖方法: userDAO.modify ArgumentCaptor<UserDO> userModifyCaptor = ArgumentCaptor.forClass(UserDO.class); Mockito.verify(userDAO).modify(userModifyCaptor.capture()); text = ResourceHelper.getResourceAsString(getClass(), path + 'userModifyDO.json'); Assert.assertEquals('用户修改不一致', text, JSON.toJSONString(userModifyCaptor.getValue()));
// 验证依赖对象 // 验证依赖对象: idGenerator Mockito.verifyZeroInteractions(idGenerator); // 验证依赖对象: userDAO Mockito.verifyNoMoreInteractions(userDAO); }
/** * 测试: 创建用户-异常 */ @Test public void testCreateUserWithException() { // 注入依赖对象 Whitebox.setInternalState(userService, 'canModify', Boolean.FALSE);
// 模拟依赖方法 // 模拟依赖方法: userDAO.getByName Long userId = 1L; Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());
// 调用测试方法 String path = RESOURCE_PATH + 'testCreateUserWithException/'; String text = ResourceHelper.getResourceAsString(getClass(), path + 'userCreateVO.json'); UserVO userCreate = JSON.parseObject(text, UserVO.class); UnsupportedOperationException exception = Assert.assertThrows('返回异常不一致', UnsupportedOperationException.class, () -> userService.createUser(userCreate)); Assert.assertEquals('异常消息不一致', '不支持修改', exception.getMessage());
// 验证依赖方法 // 验证依赖方法: userDAO.getByName Mockito.verify(userDAO).getIdByName(userCreate.getName());
// 验证依赖对象 // 验证依赖对象: idGenerator Mockito.verifyZeroInteractions(idGenerator); // 验证依赖对象: userDAO Mockito.verifyNoMoreInteractions(userDAO); }
}资源文件目录

POM文件配置
<?xml version='1.0' encoding='UTF-8' ?><project xmlns='http://maven.apache.org/POM/4.0.0' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd'> ...
<!-- 属性管理 --> <properties> ... <junit.version>4.13.1</junit.version> <mockito.version>3.3.3</mockito.version> <powermock.version>2.0.9</powermock.version> </properties>
<!-- 依赖管理 --> <dependencyManagement> <dependencies> ... <!-- PowerMock --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement>
<!-- 构建管理 --> <build> <pluginManagement> <plugins> ... <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.6</version> <executions> ... <execution> <id>copy-test-resources</id> <phase>compile</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <encoding>UTF-8</encoding> <outputDirectory>${project.build.directory}/test-classes</outputDirectory> <resources> <resource> <directory>src/test/java</directory> <includes> <include>**/*.txt</include> <include>**/*.csv</include> <include>**/*.json</include> <include>**/*.properties</include> </includes> </resource> <resource> <directory>src/test/resources</directory> <includes> <include>**/*.txt</include> <include>**/*.csv</include> <include>**/*.json</include> <include>**/*.properties</include> </includes> </resource> </resources> </configuration> </execution> </executions> </plugin> </plugins> </pluginManagement> </build></project>在属性配置中,配置了单元测试所依赖的包版本;
在依赖配置中,配置了单元测试所依赖的包名称;
在构建配置中,配置了编译时需要拷贝目录下的资源文件(如果有其它的资源文件格式,需要在pom中配置添加)。
工具类代码
/** * 资源辅助类 */public final class ResourceHelper {
/** * 构造方法 */ private ResourceHelper() { throw new UnsupportedOperationException(); }
/** * 以字符串方式获取资源 * * @param clazz 类 * @param name 资源名称 * @return 字符串 */ public static <T> String getResourceAsString(Class<T> clazz, String name) { try (InputStream is = clazz.getResourceAsStream(name)) { return IOUtils.toString(is, StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(String.format('以字符串方式获取资源(%s)异常', name), e); } }
}5 JSON资源文件的来源
来源于自己组装
[{'name':'Changyi','title':'Java Developer'...},{'name':'Tester','title':'Java Tester'...},...]来源于代码生成
public static void main(String[] args) { List<UserCreateVO> userCreateList = new ArrayList<>(); UserCreateVO userCreate0 = new UserCreateVO(); userCreate0.setName('Changyi'); userCreate0.setTitle('Java Developer'); ... // 约几十行 userCreateList.add(userCreate0); UserCreateVO userCreate1 = new UserCreateVO(); userCreate1.setName('Tester'); userCreate1.setTitle('Java Tester'); ... // 约几十行 userCreateList.add(userCreate1); ... // 约几十条 System.out.println(JSON.toJSONString(userCreateList));}[{'name':'Changyi','title':'Java Developer'...},{'name':'Tester','title':'Java Tester'...},...]来源于线上日志
2021-08-31 18:55:40,867 INFO [UserService.java:34] - 根据公司标识(1)查询所有用户:[{'id':1,'name':'Changyi','title':'Java Developer'...},{'id':2,'name':'Tester','title':'Java Tester'...},...]来源于集成测试
/** * 用户DAO测试类 */@Slf4j@RunWith(PandoraBootRunner.class)@DelegateTo(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = {ExampleApplication.class})public class UserDaoTest {
/** 用户DAO */ @Resource private UserDAO userDAO;
/** * 测试: 根据公司标识查询 */ @Test public void testQueryByCompanyId() { Long companyId = 1L; List<UserDO> userList = userDAO.queryByCompanyId(companyId); log.info('userList={}', JSON.toJSONString(userList)); }
}2021-08-31 18:55:40,867 INFO [UserDaoTest.java:24] - userList=[{'id':1,'name':'Changyi','title':'Java Developer'...},{'id':2,'name':'Tester','title':'Java Tester'...},...]首先,在源代码中添加日志输出语句; 然后,执行单元测试用例,得到对应的方法调用参数值和返回值; 最后,删除源代码中日志输出语句,恢复源代码为原来的样子。
来源于测试过程
public void batchCreate(List<UserCreate> createList) { List<UserDO> userList = createList.stream() .map(UserService::convertUser).collect(Collectors.toList()); userDAO.batchCreate(userList);}@Testpublic void testBatchCreate() { // 调用测试方法 List<UserCreate> createList = ...; userService.batchCreate(createList); // 验证测试方法 ArgumentCaptor<List<UserDO>> userListCaptor = CastUtils.cast(ArgumentCaptor.forClass(List.class)); Mockito.verify(userDAO).batchCreate(userListCaptor.capture()); Assert.assertEquals('用户列表不一致', '', JSON.toJSONString(userListCaptor.getValue()));}org.junit.ComparisonFailure: 用户列表不一致 expected:<[]> but was:<[[{'name':'Changyi','title':'Java Developer'...},{'name':'Tester','title':'Java Tester'...},...]]>6 JSON序列化技巧
序列化对象
UserVO user = ...;String text = JSON.toJSONString(user);序列化数组
UserVO[] users = ...;String text = JSON.toJSONString(users);序列化集合
List<UserVO> userList = ...;String text = JSON.toJSONString(userList);序列化映射
Map<Long, UserVO> userMap = ...;String text = JSON.toJSONString(userMap, SerializerFeature.MapSortField);序列化模板对象
Result<UserVO> result = ...;String text = JSON.toJSONString(result);序列化指定属性字段
1)指定所有类的属性字段
UserVO user = ...;SimplePropertyPreFilter filter = new SimplePropertyPreFilter();filter.getIncludes().addAll(Arrays.asList('id', 'name'));String text = JSON.toJSONString(user, filter);2)指定单个类的属性字段
List<UserVO> userList = ...;SimplePropertyPreFilter filter = new SimplePropertyPreFilter(UserVO.class);filter.getIncludes().addAll(Arrays.asList('id', 'name'));String text = JSON.toJSONString(userList, filter);3)指定多个类的属性字段
Pair<UserVO, CompanyVO> userCompanyPair = ...;SimplePropertyPreFilter userFilter = new SimplePropertyPreFilter(UserVO.class);userFilter.getUncludes().addAll(Arrays.asList('id', 'name'));SimplePropertyPreFilter companyFilter = new SimplePropertyPreFilter(CompanyVO.class);companyFilter.getIncludes().addAll(Arrays.asList('id', 'name'));String text = JSON.toJSONString(userCompanyPair, new SerializeFilter[]{userFilter, companyFilter});序列化字段排除属性字段
1)排除所有类的属性字段
UserVO user = ...;SimplePropertyPreFilter filter = new SimplePropertyPreFilter();filter.getExcludes().addAll(Arrays.asList('gmtCreate', 'gmtModified'));String text = JSON.toJSONString(user, filter);2)排除单个类的属性字段
List<UserVO> userList = ...;SimplePropertyPreFilter filter = new SimplePropertyPreFilter(UserVO.class);filter.getExcludes().addAll(Arrays.asList('gmtCreate', 'gmtModified'));String text = JSON.toJSONString(userList, filter);3)排除多个类的属性字段
Pair<UserVO, CompanyVO> userCompanyPair = ...;SimplePropertyPreFilter userFilter = new SimplePropertyPreFilter(UserVO.class);userFilter.getExcludes().addAll(Arrays.asList('gmtCreate', 'gmtModified'));SimplePropertyPreFilter companyFilter = new SimplePropertyPreFilter(CompanyVO.class);companyFilter.getExcludes().addAll(Arrays.asList('createTime', 'modifyTime'));String text = JSON.toJSONString(userCompanyPair, new SerializeFilter[]{userFilter, companyFilter});自定义序列化
1)全局配置序列化器
Geometry geometry = ...;SerializeConfig.getGlobalInstance().put(Geometry.class, new GeometrySerializer());String text = JSON.toJSONString(geometry);2)特定配置序列化器
Geometry geometry = ...;SerializeConfig config = new SerializeConfig();config.put(Geometry.class, new GeometrySerializer());String text = JSON.toJSONString(geometry, config);3)注解配置序列化器
public class User { ... @JSONField(serializeUsing = GeometrySerializer.class) private Geometry location; ...}
User user = ...;String text = JSON.toJSONString(user);7 JSON反序列化技巧
反序列化对象
String text = ...;UserVO user = JSON.parseObject(text, UserVO.class);反序列化数组
String text = ...;UserVO[] users = JSON.parseObject(text, UserVO[].class);反序列化集合
String text = ...;List<UserVO> userList = JSON.parseArray(text, UserVO.class);String text = ...;Set<UserVO> userSet = JSON.parseObject(text, new TypeReference<Set<UserVO>>() {});反序列化映射
String text = ...;Map<Long, UserVO> userList = JSON.parseObject(text, new TypeReference<Map<Long, UserVO>>() {});反序列化模板对象
String text = ...;Result<UserVO> result = JSON.parseArray(text, new TypeReference<Result<UserVO>>() {});反序列化非公有字段
String text = ...;UserVO user = JSON.parseObject(text, UserVO.class, Feature.SupportNonPublicField);反序列化Builder模式类
com.alibaba.Fastjson.JSONException: default constructor not found. class com.example.UserString text = ...;User user = JSON.parseObject(text, User.UserBuilder.class, Feature.SupportNonPublicField).build();反序列化丢失字段值
@Getter@Setter@ToStringclass User { private Long id; private String name; public User(Long id) { this.id = id; }}
String text = '{\'id\':123,\'name\':\'test\'}';User user = JSON.parseObject(text, User.class); // 会丢失name值自定义反序列化器
1)全局配置反序列化器
String text = ...;ParserConfig.getGlobalInstance().putDeserializer(Geometry.class, new GeometryDeserializer());Geometry geometry = JSON.parseObject(text, Geometry.class);2)特定配置反序列化器
String text = ...;ParserConfig config = new ParserConfig();config.putDeserializer(Geometry.class, new GeometryDeserializer());Geometry geometry = JSON.parseObject(text, Geometry.class, config);3)注解配置反序列化器
public class User { ... @JSONField(deserializeUsing = GeometryDeserializer.class) private Geometry location; ...}
String text = ...;User user = JSON.parseObject(text, User.class);8 不必要的JSON序列化
完全透传的对象
1)完全透传的参数对象
public void batchCreate(List<UserCreate> createList) { userDAO.batchCreate(createList);}@Testpublic void testBatchCreate() { // 调用测试方法 List<UserCreate> createList = new ArrayList<>(); userService.batchCreate(createList); // 验证测试方法 Mockito.verify(userDAO).batchCreate(createList);}2)完全透传的返回对象
public List<UserVO> queryByCompanyId(Long companyId) { return userDAO.queryByCompanyId(companyId);}@Testpublic void testQueryByCondition() { // 模拟依赖方法 Long companyId = 1L; List<UserVO> userList = new ArrayList<>(); Mockito.doReturn(userList).when(userDAO).queryByCompanyId(companyId); // 调用测试方法 Assert.assertEquals('用户列表不一致', userList, userService.queryByCompanyId(companyId));}完全透传的属性
1)完全透传的参数值属性
public void handleResult(Result<UserVO> result) { if (!result.isSuccess()) { metaProducer.sendCouponMessage(result.getData()); }}@Testpublic void testHandleResultWithSuccess() { // 调用测试方法 UserVO user = new UserVO(); Result<UserVO> result = Result.success(user); userService.handleResult(result);
// 验证依赖方法 Mockito.verify(metaProducer).sendCouponMessage(user);}2)完全透传的返回值属性
public UserVO get(Long userId) { Result<UserVO> result = userHsfService.get(userId); if (!result.isSuccess()) { throw new ExmapleException(String.format('获取用户(%s)失败:%s', userId, result.getMessage())); } return result.getData();}@Testpublic void testGetWithSuccess() { // 模拟依赖方法 Long userId = 123L; UserVO user = UserVO(); Mockito.doReturn(Result.success(user)).when(userHsfService).get(userId); // 调用测试方法 Assert.assertEquals('用户信息不一致', user, userService.get(userId));}仅用少数字段的对象
1)仅用少数字段的参数值对象
public void create(UserCreate userCreate) { Boolean exist = userDAO.existByName(userCreate.getName()); if (Boolean.TRUE.equals(exist)) { throw new ExmapleException(String.format('用户(%s)已存在', userCreate.getName())); } userDAO.create(userCreate);}@Testpublic void testCreateWithException() { UserCreate userCreate = new UserCreate(); userCreate.setName('changyi'); ExmapleException exception = Assert.assertThrows('异常类型不一致', ExmapleException.class, () -> userService.create(userCreate)); Assert.assertEquals('异常消息不一致', String.format('用户(%s)已存在', userCreate.getName()), exception.getMessage());}2)仅用少数字段的返回值对象
public boolean isVip(Long userId) { UserDO user = userDAO.get(userId); return VIP_ROLE_ID_SET.contains(user.getRoleId());}@Testpublic void testIsVipWithTrue() { // 模拟依赖方法 Long userId = 123L; UserDO user = new UserDO(); user.setRoleId(VIP_ROLE_ID); Mockito.doReturn(user).when(userDAO).get(userId);
// 调用测试方法 Assert.assertTrue('返回值不为真', userService.isVip());}使用new还是mock初始化对象?

JSON反序列化字符串为数据对象,大大减少了数据对象的模拟代码; JSON序列化数据对象为字符串,把数据对象验证简化为字符串验证,大大减少了数据对象的验证代码。
模拟方法返回多个值
String text = ResourceHelper.getResourceAsString(getClass(), path + 'recordList.json');Record[] records = JSON.parseObject(text, Record[].class);Mockito.doReturn(records[0], ArrayUtils.subarray(records, 1, records.length)).when(recordReader).read();模拟方法返回对应值
String text = ResourceHelper.getResourceAsString(getClass(), path + 'roleMap.json');Map<Long, String> roleIdMap = JSON.parseObject(text, new TypeReference<Map<Long, String>>() {});Mockito.doAnswer(invocation -> userMap.get(invocation.getArgument(0))).when(roleService).get(roleId);验证多次方法调用参数
ArgumentCaptor<UserCreateVO> userCreateCaptor = ArgumentCaptor.forClass(UserCreateVO.class);Mockito.verify(userDAO, Mockito.atLeastOnce()).create(userCreateCaptor.capture());String text = ResourceHelper.getResourceAsString(getClass(), path + 'userCreateList.json');Assert.assertEquals('用户创建列表不一致', text, JSON.toJSONString(userCreateCaptor.getAllValues()));数据库核心概念
赞 (0)
