Browse Source

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

YunaiV 4 years ago
parent
commit
2f0d7e8aba

+ 6 - 0
pom.xml

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

+ 1 - 1
src/main/java/cn/iocoder/dashboard/framework/redis/config/RedisConfig.java

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

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/infra/enums/InfErrorCodeConstants.java

@@ -11,7 +11,7 @@ public interface InfErrorCodeConstants {
 
     // ========== 参数配置 1001000000 ==========
     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_GET_VALUE_ERROR_IF_SENSITIVE = new ErrorCode(1001000004, "不允许获取敏感配置到前端");
 

+ 2 - 2
src/main/java/cn/iocoder/dashboard/modules/infra/service/config/impl/InfConfigServiceImpl.java

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

+ 81 - 0
src/test/java/cn/iocoder/dashboard/modules/infra/service/config/InfConfigServiceImplTest.java

@@ -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);
+    }
+
+}

+ 5 - 1
src/test/java/cn/iocoder/dashboard/modules/tool/dal/mysql/coegen/ToolInformationSchemaColumnMapperTest.java

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

+ 54 - 0
src/test/java/cn/iocoder/dashboard/util/AssertUtils.java

@@ -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(), "错误提示不匹配");
+    }
+
+}

+ 110 - 0
src/test/resources/application-unit-test.yaml

@@ -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 的请求

+ 0 - 44
src/test/resources/application.yaml

@@ -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

+ 9 - 0
src/test/resources/sql/clean.sql

@@ -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";

+ 102 - 0
src/test/resources/sql/create_tables.sql

@@ -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 '菜单权限表';