完成 config 模块的单元测试

This commit is contained in:
YunaiV 2021-03-06 13:05:13 +08:00
parent 72817a8632
commit 95857ace9a
6 changed files with 211 additions and 24 deletions

View File

@ -2,6 +2,7 @@ package cn.iocoder.dashboard.common.exception.util;
import cn.iocoder.dashboard.common.exception.ErrorCode; import cn.iocoder.dashboard.common.exception.ErrorCode;
import cn.iocoder.dashboard.common.exception.ServiceException; import cn.iocoder.dashboard.common.exception.ServiceException;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -91,7 +92,8 @@ public class ServiceExceptionUtil {
* @param params 参数 * @param params 参数
* @return 格式化后的提示 * @return 格式化后的提示
*/ */
private static String doFormat(int code, String messagePattern, Object... params) { @VisibleForTesting
public static String doFormat(int code, String messagePattern, Object... params) {
StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
int i = 0; int i = 0;
int j; int j;

View File

@ -13,14 +13,17 @@ import java.io.Serializable;
@Data @Data
public class PageParam implements Serializable { public class PageParam implements Serializable {
private static final Integer PAGE_NO = 1;
private static final Integer PAGE_SIZE = 10;
@ApiModelProperty(value = "页码,从 1 开始", required = true,example = "1") @ApiModelProperty(value = "页码,从 1 开始", required = true,example = "1")
@NotNull(message = "页码不能为空") @NotNull(message = "页码不能为空")
@Min(value = 1, message = "页码最小值为 1") @Min(value = 1, message = "页码最小值为 1")
private Integer pageNo; private Integer pageNo = PAGE_NO;
@ApiModelProperty(value = "每页条数,最大值为 100", required = true, example = "10") @ApiModelProperty(value = "每页条数,最大值为 100", required = true, example = "10")
@NotNull(message = "每页条数不能为空") @NotNull(message = "每页条数不能为空")
@Range(min = 1, max = 100, message = "条数范围为 [1, 100]") @Range(min = 1, max = 100, message = "条数范围为 [1, 100]")
private Integer pageSize; private Integer pageSize = PAGE_SIZE;
} }

View File

@ -1,6 +1,7 @@
package cn.iocoder.dashboard.util.date; package cn.iocoder.dashboard.util.date;
import java.time.Duration; import java.time.Duration;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
/** /**
@ -22,4 +23,40 @@ public class DateUtils {
return endTime.getTime() - startTime.getTime(); return endTime.getTime() - startTime.getTime();
} }
/**
* 创建指定时间
*
* @param year
* @param mouth
* @param day
* @return 指定时间
*/
public static Date buildTime(int year, int mouth, int day) {
return buildTime(year, mouth, day, 0, 0, 0);
}
/**
* 创建指定时间
*
* @param year
* @param mouth
* @param day
* @param hour 小时
* @param minute 分钟
* @param second
* @return 指定时间
*/
public static Date buildTime(int year, int mouth, int day,
int hour, int minute, int second) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, mouth - 1);
calendar.set(Calendar.DAY_OF_MONTH, day);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, second);
calendar.set(Calendar.MILLISECOND, 0); // 一般情况下都是 0 毫秒
return calendar.getTime();
}
} }

View File

@ -0,0 +1,22 @@
package cn.iocoder.dashboard.util.object;
import cn.hutool.core.util.ObjectUtil;
import java.util.function.Consumer;
/**
* Object 工具类
*
* @author 芋道源码
*/
public class ObjectUtils {
public static <T> T clone(T object, Consumer<T> consumer) {
T result = ObjectUtil.clone(object);
if (result != null) {
consumer.accept(result);
}
return result;
}
}

View File

@ -1,26 +1,31 @@
package cn.iocoder.dashboard.modules.infra.service.config; package cn.iocoder.dashboard.modules.infra.service.config;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.dashboard.BaseSpringBootUnitTest; import cn.iocoder.dashboard.BaseSpringBootUnitTest;
import cn.iocoder.dashboard.common.exception.ServiceException; import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigCreateReqVO; import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigCreateReqVO;
import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigExportReqVO;
import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigPageReqVO;
import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigUpdateReqVO; import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigUpdateReqVO;
import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO; import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO;
import cn.iocoder.dashboard.modules.infra.dal.mysql.config.InfConfigMapper; import cn.iocoder.dashboard.modules.infra.dal.mysql.config.InfConfigMapper;
import cn.iocoder.dashboard.modules.infra.enums.config.InfConfigTypeEnum; import cn.iocoder.dashboard.modules.infra.enums.config.InfConfigTypeEnum;
import cn.iocoder.dashboard.modules.infra.mq.producer.config.InfConfigProducer; import cn.iocoder.dashboard.modules.infra.mq.producer.config.InfConfigProducer;
import cn.iocoder.dashboard.modules.infra.service.config.impl.InfConfigServiceImpl; import cn.iocoder.dashboard.modules.infra.service.config.impl.InfConfigServiceImpl;
import cn.iocoder.dashboard.util.object.ObjectUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import static cn.hutool.core.util.RandomUtil.randomEle; import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.CONFIG_KEY_DUPLICATE; import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.*;
import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.CONFIG_NOT_EXISTS;
import static cn.iocoder.dashboard.util.AssertUtils.assertExceptionEquals;
import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals; import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
import static cn.iocoder.dashboard.util.RandomUtils.randomPojo; import static cn.iocoder.dashboard.util.RandomUtils.randomPojo;
import static cn.iocoder.dashboard.util.date.DateUtils.buildTime;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -36,11 +41,92 @@ public class InfConfigServiceImplTest extends BaseSpringBootUnitTest {
@MockBean @MockBean
private InfConfigProducer configProducer; private InfConfigProducer configProducer;
@Test
public void testGetConfigPage() {
// mock 数据
InfConfigDO dbConfig = randomInfConfigDO(o -> { // 等会查询到
o.setName("芋艿");
o.setKey("yunai");
o.setType(InfConfigTypeEnum.SYSTEM.getType());
o.setCreateTime(buildTime(2021, 2, 1));
});
configMapper.insert(dbConfig);
// 测试 name 不匹配
configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setName("土豆")));
// 测试 key 不匹配
configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setKey("tudou")));
// 测试 type 不匹配
configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setType(InfConfigTypeEnum.CUSTOM.getType())));
// 测试 createTime 不匹配
configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setCreateTime(buildTime(2021, 1, 1))));
// 准备参数
InfConfigPageReqVO reqVO = new InfConfigPageReqVO();
reqVO.setName("");
reqVO.setKey("nai");
reqVO.setType(InfConfigTypeEnum.SYSTEM.getType());
reqVO.setBeginTime(buildTime(2021, 1, 15));
reqVO.setEndTime(buildTime(2021, 2, 15));
// 调用
PageResult<InfConfigDO> pageResult = configService.getConfigPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbConfig, pageResult.getList().get(0));
}
@Test
public void testGetConfigList() {
// mock 数据
InfConfigDO dbConfig = randomInfConfigDO(o -> { // 等会查询到
o.setName("芋艿");
o.setKey("yunai");
o.setType(InfConfigTypeEnum.SYSTEM.getType());
o.setCreateTime(buildTime(2021, 2, 1));
});
configMapper.insert(dbConfig);
// 测试 name 不匹配
configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setName("土豆")));
// 测试 key 不匹配
configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setKey("tudou")));
// 测试 type 不匹配
configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setType(InfConfigTypeEnum.CUSTOM.getType())));
// 测试 createTime 不匹配
configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setCreateTime(buildTime(2021, 1, 1))));
// 准备参数
InfConfigExportReqVO reqVO = new InfConfigExportReqVO();
reqVO.setName("");
reqVO.setKey("nai");
reqVO.setType(InfConfigTypeEnum.SYSTEM.getType());
reqVO.setBeginTime(buildTime(2021, 1, 15));
reqVO.setEndTime(buildTime(2021, 2, 15));
// 调用
List<InfConfigDO> list = configService.getConfigList(reqVO);
// 断言
assertEquals(1, list.size());
assertPojoEquals(dbConfig, list.get(0));
}
@Test
public void testGetConfigByKey() {
// mock 数据
InfConfigDO dbConfig = randomInfConfigDO();
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
String key = dbConfig.getKey();
// 调用
InfConfigDO config = configService.getConfigByKey(key);
// 断言
assertNotNull(config);
assertPojoEquals(dbConfig, config);
}
@Test @Test
public void testCreateConfig_success() { public void testCreateConfig_success() {
// 准备参数 // 准备参数
InfConfigCreateReqVO reqVO = randomPojo(InfConfigCreateReqVO.class); InfConfigCreateReqVO reqVO = randomPojo(InfConfigCreateReqVO.class);
// mock
// 调用 // 调用
Long configId = configService.createConfig(reqVO); Long configId = configService.createConfig(reqVO);
@ -63,11 +149,8 @@ public class InfConfigServiceImplTest extends BaseSpringBootUnitTest {
o.setKey(reqVO.getKey()); // 模拟 key 重复 o.setKey(reqVO.getKey()); // 模拟 key 重复
})); }));
// 调用 // 调用, 并断言异常
ServiceException serviceException = assertThrows(ServiceException.class, assertServiceException(() -> configService.createConfig(reqVO), CONFIG_KEY_DUPLICATE);
() -> configService.createConfig(reqVO));
// 断言异常
assertExceptionEquals(CONFIG_KEY_DUPLICATE, serviceException);
} }
@Test @Test
@ -94,20 +177,51 @@ public class InfConfigServiceImplTest extends BaseSpringBootUnitTest {
// 准备参数 // 准备参数
InfConfigUpdateReqVO reqVO = randomPojo(InfConfigUpdateReqVO.class); InfConfigUpdateReqVO reqVO = randomPojo(InfConfigUpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> configService.updateConfig(reqVO), CONFIG_NOT_EXISTS);
}
@Test
public void testDeleteConfig_success() {
// mock 数据
InfConfigDO dbConfig = randomInfConfigDO(o -> {
o.setType(InfConfigTypeEnum.CUSTOM.getType()); // 只能删除 CUSTOM 类型
});
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbConfig.getId();
// 调用 // 调用
ServiceException serviceException = assertThrows(ServiceException.class, configService.deleteConfig(id);
() -> configService.updateConfig(reqVO)); // 校验数据不存在了
// 断言异常 assertNull(configMapper.selectById(id));
assertExceptionEquals(CONFIG_NOT_EXISTS, serviceException); // 校验调用
verify(configProducer, times(1)).sendConfigRefreshMessage();
}
@Test
public void testDeleteConfig_canNotDeleteSystemType() {
// mock 数据
InfConfigDO dbConfig = randomInfConfigDO(o -> {
o.setType(InfConfigTypeEnum.SYSTEM.getType()); // SYSTEM 不允许删除
});
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbConfig.getId();
// 调用, 并断言异常
assertServiceException(() -> configService.deleteConfig(id), CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE);
} }
// ========== 随机对象 ========== // ========== 随机对象 ==========
@SafeVarargs @SafeVarargs
@SuppressWarnings("unchecked")
private static InfConfigDO randomInfConfigDO(Consumer<InfConfigDO>... consumers) { private static InfConfigDO randomInfConfigDO(Consumer<InfConfigDO>... consumers) {
InfConfigDO config = randomPojo(InfConfigDO.class, consumers); Consumer<InfConfigDO> consumer = (o) -> {
config.setType(randomEle(InfConfigTypeEnum.values()).getType()); // 保证 key 的范围 o.setType(randomEle(InfConfigTypeEnum.values()).getType()); // 保证 key 的范围
return config; };
return randomPojo(InfConfigDO.class, ArrayUtil.append(new Consumer[]{consumer}, consumers));
} }
} }

View File

@ -4,11 +4,15 @@ import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.dashboard.common.exception.ErrorCode; import cn.iocoder.dashboard.common.exception.ErrorCode;
import cn.iocoder.dashboard.common.exception.ServiceException; import cn.iocoder.dashboard.common.exception.ServiceException;
import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.function.Executable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays; import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertThrows;
/** /**
* 单元测试assert 断言工具类 * 单元测试assert 断言工具类
* *
@ -47,14 +51,19 @@ public class AssertUtils {
} }
/** /**
* 比对抛出的 ServiceException 是否匹配 * 执行方法校验抛出的 Service 是否符合条件
* *
* @param executable 业务异常
* @param errorCode 错误码对象 * @param errorCode 错误码对象
* @param serviceException 业务异常 * @param messageParams 消息参数
*/ */
public static void assertExceptionEquals(ErrorCode errorCode, ServiceException serviceException) { public static void assertServiceException(Executable executable, ErrorCode errorCode, Object... messageParams) {
// 调用方法
ServiceException serviceException = assertThrows(ServiceException.class, executable);
// 校验错误码
Assertions.assertEquals(errorCode.getCode(), serviceException.getCode(), "错误码不匹配"); Assertions.assertEquals(errorCode.getCode(), serviceException.getCode(), "错误码不匹配");
Assertions.assertEquals(errorCode.getMessage(), serviceException.getMessage(), "错误提示不匹配"); String message = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMessage(), messageParams);
Assertions.assertEquals(message, serviceException.getMessage(), "错误提示不匹配");
} }
} }