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 develop
error: xxx/xxx: Filename too long
git add .
error: open('xxx/xxx'): Filename too long
error: unable to index file 'xxx/xxx'
fatal: adding files failed
git config --system core.longpaths true
JSON资源文件格式
4 测试资源使用案例
被测案例代码
/**
* 用户服务类
*/
@Service
public 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);
}
@Test
public 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.User
String text = ...;
User user = JSON.parseObject(text, User.UserBuilder.class, Feature.SupportNonPublicField).build();
反序列化丢失字段值
@Getter
@Setter
@ToString
class 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);
}
@Test
public 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);
}
@Test
public 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());
}
}
@Test
public 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();
}
@Test
public 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);
}
@Test
public 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());
}
@Test
public 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)