单元测试,接入 h2 实现单元测试

This commit is contained in:
YunaiV 2021-02-28 13:29:24 +08:00
parent 99855e7cdc
commit 2f0d7e8aba
11 changed files with 371 additions and 49 deletions

View File

@ -177,6 +177,12 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.h2database</groupId> <!-- 单元测试,我们采用 H2 作为数据库 -->
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<!-- 工具类相关 --> <!-- 工具类相关 -->
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>

View File

@ -27,7 +27,7 @@ public class RedisConfig {
template.setConnectionFactory(factory); template.setConnectionFactory(factory);
// 使用 String 序列化方式序列化 KEY // 使用 String 序列化方式序列化 KEY
template.setKeySerializer(RedisSerializer.string()); template.setKeySerializer(RedisSerializer.string());
// 使用 JSON 序列化方式库是 FastJSON 序列化 VALUE // 使用 JSON 序列化方式库是 Jackson 序列化 VALUE
template.setValueSerializer(RedisSerializer.json()); template.setValueSerializer(RedisSerializer.json());
return template; return template;
} }

View File

@ -11,7 +11,7 @@ public interface InfErrorCodeConstants {
// ========== 参数配置 1001000000 ========== // ========== 参数配置 1001000000 ==========
ErrorCode CONFIG_NOT_FOUND = new ErrorCode(1001000001, "参数配置不存在"); ErrorCode CONFIG_NOT_FOUND = new ErrorCode(1001000001, "参数配置不存在");
ErrorCode CONFIG_NAME_DUPLICATE = new ErrorCode(1001000002, "参数配置 key 重复"); ErrorCode CONFIG_KEY_DUPLICATE = new ErrorCode(1001000002, "参数配置 key 重复");
ErrorCode CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE = new ErrorCode(1001000003, "不能删除类型为系统内置的参数配置"); ErrorCode CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE = new ErrorCode(1001000003, "不能删除类型为系统内置的参数配置");
ErrorCode CONFIG_GET_VALUE_ERROR_IF_SENSITIVE = new ErrorCode(1001000004, "不允许获取敏感配置到前端"); ErrorCode CONFIG_GET_VALUE_ERROR_IF_SENSITIVE = new ErrorCode(1001000004, "不允许获取敏感配置到前端");

View File

@ -117,10 +117,10 @@ public class InfConfigServiceImpl implements InfConfigService {
} }
// 如果 id 为空说明不用比较是否为相同 id 的参数配置 // 如果 id 为空说明不用比较是否为相同 id 的参数配置
if (id == null) { if (id == null) {
throw ServiceExceptionUtil.exception(CONFIG_NAME_DUPLICATE); throw ServiceExceptionUtil.exception(CONFIG_KEY_DUPLICATE);
} }
if (!config.getId().equals(id)) { if (!config.getId().equals(id)) {
throw ServiceExceptionUtil.exception(CONFIG_NAME_DUPLICATE); throw ServiceExceptionUtil.exception(CONFIG_KEY_DUPLICATE);
} }
} }

View File

@ -0,0 +1,81 @@
package cn.iocoder.dashboard.modules.infra.service.config;
import cn.iocoder.dashboard.common.exception.ServiceException;
import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigCreateReqVO;
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.enums.config.InfConfigTypeEnum;
import cn.iocoder.dashboard.modules.infra.mq.producer.config.InfConfigProducer;
import cn.iocoder.dashboard.modules.infra.service.config.impl.InfConfigServiceImpl;
import cn.iocoder.dashboard.util.AssertUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import javax.annotation.Resource;
import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.CONFIG_KEY_DUPLICATE;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@SpringBootTest
@ActiveProfiles("unit-test")
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public class InfConfigServiceImplTest {
@Resource
private InfConfigServiceImpl configService;
@Resource
private InfConfigMapper configMapper;
@MockBean
private InfConfigProducer configProducer;
@Test
public void testCreateConfig_success() {
// 入参
InfConfigCreateReqVO reqVO = new InfConfigCreateReqVO();
reqVO.setGroup("test_group");
reqVO.setName("test_name");
reqVO.setValue("test_value");
reqVO.setSensitive(true);
reqVO.setRemark("test_remark");
reqVO.setKey("test_key");
// mock
// 调用
Long configId = configService.createConfig(reqVO);
// 校验
assertNotNull(configId);
// 校验记录的属性是否正确
InfConfigDO config = configMapper.selectById(configId);
AssertUtils.assertEquals(reqVO, config);
assertEquals(InfConfigTypeEnum.CUSTOM.getType(), config.getType());
// 校验调用
verify(configProducer, times(1)).sendConfigRefreshMessage();
}
@Test
@Sql(statements = "INSERT INTO `inf_config`(`group`, `type`, `name`, `key`, `value`, `sensitive`) VALUES ('test_group', 1, 'test_name', 'test_key', 'test_value', 1);")
public void testCreateConfig_keyDuplicate() {
// 入参
InfConfigCreateReqVO reqVO = new InfConfigCreateReqVO();
reqVO.setGroup("test_group");
reqVO.setName("test_name");
reqVO.setValue("test_value");
reqVO.setSensitive(true);
reqVO.setRemark("test_remark");
reqVO.setKey("test_key");
// mock
// 调用
ServiceException serviceException = assertThrows(ServiceException.class, () -> configService.createConfig(reqVO));
// 断言
AssertUtils.assertEquals(CONFIG_KEY_DUPLICATE, serviceException);
}
}

View File

@ -3,19 +3,23 @@ package cn.iocoder.dashboard.modules.tool.dal.mysql.coegen;
import cn.iocoder.dashboard.modules.tool.dal.dataobject.codegen.ToolSchemaColumnDO; import cn.iocoder.dashboard.modules.tool.dal.dataobject.codegen.ToolSchemaColumnDO;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest @SpringBootTest
@ActiveProfiles("unit-test")
public class ToolInformationSchemaColumnMapperTest { public class ToolInformationSchemaColumnMapperTest {
@Resource @Resource
private ToolSchemaColumnMapper toolInformationSchemaColumnMapper; private ToolSchemaColumnMapper toolInformationSchemaColumnMapper;
@Test @Test
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testSelectListByTableName() { public void testSelectListByTableName() {
List<ToolSchemaColumnDO> columns = toolInformationSchemaColumnMapper List<ToolSchemaColumnDO> columns = toolInformationSchemaColumnMapper
.selectListByTableName("inf_config"); .selectListByTableName("inf_config");

View File

@ -0,0 +1,54 @@
package cn.iocoder.dashboard.util;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.dashboard.common.exception.ErrorCode;
import cn.iocoder.dashboard.common.exception.ServiceException;
import org.junit.jupiter.api.Assertions;
import java.lang.reflect.Field;
import java.util.Arrays;
/**
* 单元测试assert 断言工具类
*
* @author 芋道源码
*/
public class AssertUtils {
/**
* 比对两个对象的属性是否一致
*
* 注意如果 expected 存在的属性actual 不存在的时候会进行忽略
*
* @param expected 期望对象
* @param actual 实际对象
*/
public static void assertEquals(Object expected, Object actual) {
Field[] expectedFields = ReflectUtil.getFields(expected.getClass());
Arrays.stream(expectedFields).forEach(expectedField -> {
// 忽略不存在的属性
Field actualField = ReflectUtil.getField(actual.getClass(), expectedField.getName());
if (actualField == null) {
return;
}
// 比对
Assertions.assertEquals(
ReflectUtil.getFieldValue(expected, expectedField),
ReflectUtil.getFieldValue(actual, actualField),
String.format("Field(%s) 不匹配", expectedField.getName())
);
});
}
/**
* 比对抛出的 ServiceException 是否匹配
*
* @param errorCode 错误码对象
* @param serviceException 业务异常
*/
public static void assertEquals(ErrorCode errorCode, ServiceException serviceException) {
Assertions.assertEquals(errorCode.getCode(), serviceException.getCode(), "错误码不匹配");
Assertions.assertEquals(errorCode.getMessage(), serviceException.getMessage(), "错误提示不匹配");
}
}

View File

@ -0,0 +1,110 @@
spring:
main:
lazy-initialization: true
# 去除的自动配置项
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration # 单元测试,禁用 SpringSecurity
- org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration # 单元测试,禁用 SpringSecurity
- org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 单元测试,禁用 Quartz
- com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration # 单元测试,禁用 Lock4j 分布式锁
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
datasource:
name: ruoyi-vue-pro
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
driver-class-name: org.h2.Driver
username: sa
password:
schema: classpath:sql/create_tables.sql # MySQL 转 H2 的语句,使用 https://www.jooq.org/translate/ 工具
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 127.0.0.1 # 地址
port: 6379 # 端口
database: 0 # 数据库索引
--- #################### 定时任务相关配置 ####################
# Quartz 配置项,对应 QuartzProperties 配置类(单元测试,禁用 Quartz
--- #################### 配置中心相关配置 ####################
# Apollo 配置中心
apollo:
bootstrap:
enabled: false # 单元测试,禁用配置中心
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项(单元测试,禁用 Lock4j
# Resilience4j 配置项
resilience4j:
ratelimiter:
instances:
backendA:
limit-for-period: 1 # 每个周期内,允许的请求数。默认为 50
limit-refresh-period: 60s # 每个周期的时长,单位:微秒。默认为 500
timeout-duration: 1s # 被限流时,阻塞等待的时长,单位:微秒。默认为 5s
register-health-indicator: true # 是否注册到健康监测
--- #################### 监控相关配置 ####################
# Actuator 监控端点的配置项
management:
endpoints:
enabled-by-default: false
# Spring Boot Admin 配置项
spring:
boot:
admin:
# Spring Boot Admin Client 客户端的相关配置
client:
enabled: false
# Spring Boot Admin Server 服务端的相关配置
context-path: /admin # 配置 Spring
# 日志文件配置
logging:
file:
path: ${user.home}/logs/ # 日志文件的路径
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
info:
version: 1.0.0
base-package: cn.iocoder.dashboard
web:
api-prefix: /api
controller-package: ${yudao.info.base-package}
security:
token-header: Authorization
token-secret: abcdefghijklmnopqrstuvwxyz
token-timeout: 1d
session-timeout: 30m
mock-enable: true
mock-secret: test
swagger:
enable: false # 单元测试,禁用 Swagger
captcha:
timeout: 5m
width: 160
height: 60
file:
base-path: http://127.0.0.1:${server.port}/${yudao.web.api-prefix}/file/get/
codegen:
base-package: ${yudao.info.base-package}.modules
db-schemas: ${spring.datasource.name}
xss:
enable: false
exclude-urls: # 如下两个 url仅仅是为了演示去掉配置也没关系
- ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求

View File

@ -1,44 +0,0 @@
spring:
application:
name: dashboard
profiles:
active: local
# Servlet 配置
servlet:
# 文件上传相关配置项
multipart:
max-file-size: 16MB # 单个文件大小
max-request-size: 32MB # 设置总上传的文件大小
# Jackson 配置项
jackson:
serialization:
write-dates-as-timestamps: true # 设置 Date 的格式,使用时间戳
write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
fail-on-empty-beans: false # 允许序列化无属性的 Bean
main:
lazy-initialization: true
# 去除的自动配置项
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
- org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration
- org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration
# MyBatis Plus 的配置项
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印日志
global-config:
db-config:
id-type: AUTO # 自增 ID
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: ${yudao.info.base-package}.modules.*.dal.dataobject

View File

@ -0,0 +1,9 @@
-- inf 开头的 DB
DELETE FROM "inf_config";
-- sys 开头的 DB
DELETE FROM "sys_dept";
DELETE FROM "sys_dict_data";
DELETE FROM "sys_role";
DELETE FROM "sys_role_menu";
DELETE FROM "sys_menu";

View File

@ -0,0 +1,102 @@
-- inf 开头的 DB
CREATE TABLE "inf_config" (
"id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"group" varchar(50) NOT NULL,
"type" tinyint NOT NULL,
"name" varchar(100) NOT NULL DEFAULT '',
"key" varchar(100) NOT NULL DEFAULT '',
"value" varchar(500) NOT NULL DEFAULT '',
"sensitive" bit NOT NULL,
"remark" varchar(500) DEFAULT NULL,
"create_by" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '参数配置表';
-- sys 开头的 DB
CREATE TABLE "sys_dept" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(30) NOT NULL DEFAULT '',
"parent_id" bigint NOT NULL DEFAULT '0',
"sort" int NOT NULL DEFAULT '0',
"leader" varchar(20) DEFAULT NULL,
"phone" varchar(11) DEFAULT NULL,
"email" varchar(50) DEFAULT NULL,
"status" tinyint NOT NULL,
"create_by" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '部门表';
CREATE TABLE "sys_dict_data" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"sort" int NOT NULL DEFAULT '0',
"label" varchar(100) NOT NULL DEFAULT '',
"value" varchar(100) NOT NULL DEFAULT '',
"dict_type" varchar(100) NOT NULL DEFAULT '',
"status" tinyint NOT NULL DEFAULT '0',
"remark" varchar(500) DEFAULT NULL,
"create_by" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '字典数据表';
CREATE TABLE "sys_role" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(30) NOT NULL,
"code" varchar(100) NOT NULL,
"sort" int NOT NULL,
"data_scope" tinyint NOT NULL DEFAULT '1',
"data_scope_dept_ids" varchar(500) NOT NULL DEFAULT '',
"status" tinyint NOT NULL,
"type" tinyint NOT NULL,
"remark" varchar(500) DEFAULT NULL,
"create_by" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '角色信息表';
CREATE TABLE "sys_role_menu" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"role_id" bigint NOT NULL,
"menu_id" bigint NOT NULL,
"create_by" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '角色和菜单关联表';
CREATE TABLE "sys_menu" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(50) NOT NULL,
"permission" varchar(100) NOT NULL DEFAULT '',
"menu_type" tinyint NOT NULL,
"sort" int NOT NULL DEFAULT '0',
"parent_id" bigint NOT NULL DEFAULT '0',
"path" varchar(200) DEFAULT '',
"icon" varchar(100) DEFAULT '#',
"component" varchar(255) DEFAULT NULL,
"status" tinyint NOT NULL DEFAULT '0',
"create_by" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_by" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '菜单权限表';