feat: 修改包名 & 增加单元测试支持

This commit is contained in:
zengxiaobo 2024-04-19 10:42:32 +08:00
parent e051c56112
commit 50a80ab86d
39 changed files with 852 additions and 41 deletions

View File

@ -1,5 +1,6 @@
package cn.axzo.foundation.exception;
import cn.axzo.foundation.result.IResultCode;
import com.google.common.base.Strings;
import java.util.Collection;

View File

@ -1,6 +1,7 @@
package cn.axzo.foundation.exception;
import cn.axzo.foundation.result.IResultCode;
import lombok.Getter;
/**

View File

@ -0,0 +1,50 @@
package cn.axzo.foundation.result;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApiResult<T> {
private static final String SUCCESS_HTTP_CODE = "200";
private static final String FAILED_HTTP_CODE = "400";
private String code;
private String msg;
private T data;
public static <T> ApiResult<T> success() {
return success(null);
}
public static <T> ApiResult<T> success(T data) {
return ApiResult.<T>builder()
.code(SUCCESS_HTTP_CODE)
.data(data)
.build();
}
public static ApiResult error(String msg) {
return ApiResult.builder()
.code(FAILED_HTTP_CODE)
.msg(msg)
.build();
}
public static ApiResult error(IResultCode resultCode) {
return error(resultCode.getErrorCode(), resultCode.getErrorMessage());
}
public static ApiResult error(String code, String msg) {
return ApiResult.builder()
.code(code)
.msg(msg)
.build();
}
}

View File

@ -1,5 +1,6 @@
package cn.axzo.foundation.exception;
package cn.axzo.foundation.result;
import cn.axzo.foundation.exception.BusinessException;
import cn.axzo.foundation.util.VarParamFormatter;
public interface IResultCode {

View File

@ -1,6 +1,5 @@
package cn.axzo.foundation.result;
import cn.axzo.foundation.exception.IResultCode;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;

View File

@ -1,8 +1,8 @@
package cn.axzo.foundation.dao.support.data.converter;
package cn.axzo.foundation.dao.support.converter;
import cn.axzo.foundation.dao.support.data.mysql.MybatisPlusConverterUtils;
import cn.axzo.foundation.dao.support.data.page.IPageReq;
import cn.axzo.foundation.dao.support.data.page.PageResp;
import cn.axzo.foundation.dao.support.mysql.MybatisPlusConverterUtils;
import cn.axzo.foundation.dao.support.page.IPageReq;
import cn.axzo.foundation.dao.support.page.PageResp;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.google.common.collect.Lists;

View File

@ -1,6 +1,6 @@
package cn.axzo.foundation.dao.support.data.mysql;
package cn.axzo.foundation.dao.support.mysql;
import cn.axzo.foundation.dao.support.data.mysql.type.SetTypeHandler;
import cn.axzo.foundation.dao.support.mysql.type.SetTypeHandler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.mysql;
package cn.axzo.foundation.dao.support.mysql;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

View File

@ -1,7 +1,7 @@
package cn.axzo.foundation.dao.support.data.mysql;
package cn.axzo.foundation.dao.support.mysql;
import cn.axzo.foundation.dao.support.data.page.IPageReq;
import cn.axzo.foundation.dao.support.data.wrapper.SimpleWrapperConverter;
import cn.axzo.foundation.dao.support.page.IPageReq;
import cn.axzo.foundation.dao.support.wrapper.SimpleWrapperConverter;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.mysql;
package cn.axzo.foundation.dao.support.mysql;
import cn.axzo.foundation.exception.Axssert;
import cn.axzo.foundation.result.ResultCode;

View File

@ -1,9 +1,9 @@
package cn.axzo.foundation.dao.support.data.mysql;
package cn.axzo.foundation.dao.support.mysql;
import cn.axzo.foundation.dao.support.data.wrapper.CriteriaWrapper;
import cn.axzo.foundation.dao.support.data.wrapper.Operator;
import cn.axzo.foundation.dao.support.data.wrapper.OperatorProcessor;
import cn.axzo.foundation.dao.support.data.wrapper.TriConsumer;
import cn.axzo.foundation.dao.support.wrapper.CriteriaWrapper;
import cn.axzo.foundation.dao.support.wrapper.Operator;
import cn.axzo.foundation.dao.support.wrapper.OperatorProcessor;
import cn.axzo.foundation.dao.support.wrapper.TriConsumer;
import cn.axzo.foundation.exception.Axssert;
import cn.axzo.foundation.exception.BusinessException;
import cn.axzo.foundation.result.ResultCode;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.mysql;
package cn.axzo.foundation.dao.support.mysql;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

View File

@ -1,7 +1,7 @@
package cn.axzo.foundation.dao.support.data.mysql;
package cn.axzo.foundation.dao.support.mysql;
import cn.axzo.foundation.dao.support.data.wrapper.CriteriaWrapper;
import cn.axzo.foundation.dao.support.data.wrapper.SimpleWrapperConverter;
import cn.axzo.foundation.dao.support.wrapper.CriteriaWrapper;
import cn.axzo.foundation.dao.support.wrapper.SimpleWrapperConverter;
import cn.axzo.foundation.exception.Axssert;
import cn.axzo.foundation.result.ResultCode;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.mysql.plugins;
package cn.axzo.foundation.dao.support.mysql.plugins;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.google.common.base.Strings;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.mysql.plugins;
package cn.axzo.foundation.dao.support.mysql.plugins;
import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.mysql.type;
package cn.axzo.foundation.dao.support.mysql.type;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.serializer.SerializerFeature;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.mysql.type;
package cn.axzo.foundation.dao.support.mysql.type;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.serializer.SerializerFeature;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.mysql.type;
package cn.axzo.foundation.dao.support.mysql.type;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.mysql.type;
package cn.axzo.foundation.dao.support.mysql.type;
import com.google.common.base.Splitter;
import com.google.common.collect.Sets;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.page;
package cn.axzo.foundation.dao.support.page;
import cn.axzo.foundation.enums.OrderEnum;
import com.google.common.collect.ImmutableList;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.page;
package cn.axzo.foundation.dao.support.page;
import com.google.common.collect.ImmutableList;
import lombok.AllArgsConstructor;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.utils;
package cn.axzo.foundation.dao.support.utils;
import cn.axzo.foundation.enums.AppEnvEnum;
import com.google.common.collect.ImmutableList;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.utils;
package cn.axzo.foundation.dao.support.utils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
@ -6,8 +6,8 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import cn.axzo.foundation.dao.support.data.mysql.MybatisPlusConverterUtils;
import cn.axzo.foundation.dao.support.data.wrapper.SimpleWrapperConverter;
import cn.axzo.foundation.dao.support.mysql.MybatisPlusConverterUtils;
import cn.axzo.foundation.dao.support.wrapper.SimpleWrapperConverter;
import com.google.common.base.Preconditions;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@ -1,10 +1,9 @@
package cn.axzo.foundation.dao.support.data.wrapper;
package cn.axzo.foundation.dao.support.wrapper;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Function;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.wrapper;
package cn.axzo.foundation.dao.support.wrapper;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.wrapper;
package cn.axzo.foundation.dao.support.wrapper;
import lombok.AllArgsConstructor;
import lombok.Getter;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.wrapper;
package cn.axzo.foundation.dao.support.wrapper;
import com.google.common.collect.ImmutableListMultimap;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.wrapper;
package cn.axzo.foundation.dao.support.wrapper;
import cn.axzo.foundation.exception.BusinessException;
import cn.axzo.foundation.result.ResultCode;

View File

@ -1,4 +1,4 @@
package cn.axzo.foundation.dao.support.data.wrapper;
package cn.axzo.foundation.dao.support.wrapper;
/**
* 仅包内可访问

View File

@ -23,6 +23,7 @@
<modules>
<module>dao-support-lib</module>
<module>common-lib</module>
<module>unittest-support-lib</module>
</modules>
<dependencies>

View File

@ -0,0 +1,90 @@
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.axzo.foundation</groupId>
<artifactId>axzo-lib-box</artifactId>
<version>2.0.0-SNAPSHOT</version>
</parent>
<artifactId>unittest-support-lib</artifactId>
<dependencies>
<dependency>
<groupId>cn.axzo.foundation</groupId>
<artifactId>common-lib</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<!-- 3rd -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.7.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,130 @@
package cn.axzo.foundation.unittest.support;
import cn.axzo.foundation.result.ApiResult;
import cn.axzo.foundation.unittest.support.config.MysqlConfig;
import cn.axzo.foundation.unittest.support.config.RedisConfig;
import cn.axzo.foundation.unittest.support.config.TestConfig;
import cn.axzo.foundation.unittest.support.config.TransactionalConfig;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import com.google.common.io.Files;
import lombok.RequiredArgsConstructor;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.util.AopTestUtils;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import org.springframework.web.context.WebApplicationContext;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
@Import(value = {TestConfig.class, MysqlConfig.class, RedisConfig.class, TransactionalConfig.class})
@ContextConfiguration
@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
@ActiveProfiles("unittest")
@ComponentScan("cn.axzo.**.config")
@RequiredArgsConstructor
public abstract class BaseTest {
private static final String JSON_FILE_CLASSPATH = "classpath:json/";
/**
* 记录对象原始的 field . 因为这些值可能被mock替换, 在每次执行单元测试前统一进行一次替换恢复
* 避免影响后续的单元测试.
*/
private static Table<Object, String, Object> ORIGIN_FIELD_TABLE = HashBasedTable.create();
protected final MockMvc mockMvc;
protected final WebApplicationContext context;
@BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
}
@AfterEach
public void teardown() {
// restore origin value of field.
ORIGIN_FIELD_TABLE.cellSet().stream().forEach(e -> {
ReflectionTestUtils.setField(e.getRowKey(), e.getColumnKey(), e.getValue());
});
}
protected <T> T unwrapProxy(Object bean) {
return AopTestUtils.getUltimateTargetObject(bean);
}
/**
* 设置对象field为mock对象. teardown时恢复成原spring代理的对象
*/
protected void setMockField(Object targetObject, String fieldName, Object value) {
// 保存一份默认值
Object oldField = ReflectionTestUtils.getField(targetObject, fieldName);
ReflectionTestUtils.setField(targetObject, fieldName, value);
if (!ORIGIN_FIELD_TABLE.contains(targetObject, fieldName) && oldField != null) {
ORIGIN_FIELD_TABLE.put(targetObject, fieldName, oldField);
}
}
protected MockHttpServletRequestBuilder buildJsonRequest(String path, Object object) {
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders
.post(path)
.contentType(MediaType.APPLICATION_JSON)
.content(JSONObject.toJSONString(object));
return builder;
}
protected <T> ApiResult<T> getResult(MvcResult result, Class<T> clz) throws Exception {
return getResult(result, new TypeReference<ApiResult<T>>(clz) {
});
}
protected <T> ApiResult<T> getResult(MvcResult result, TypeReference<ApiResult<T>> typeReference) throws Exception {
Assert.notNull(result, "Result must not be null");
String response = result.getResponse().getContentAsString();
return JSONObject.parseObject(response, typeReference);
}
public <T> T loadJson(String fileName, Class<T> clz) {
try {
String jsonString = Files.asCharSource(ResourceUtils.getFile(JSON_FILE_CLASSPATH + fileName), StandardCharsets.UTF_8).read();
return JSON.parseObject(jsonString, clz);
} catch (IOException e) {
throw new RuntimeException("loadJson got error", e);
}
}
public <T> List<T> loadJsonAsList(String fileName, Class<T> clz) {
try {
String jsonString = Files.asCharSource(ResourceUtils.getFile(JSON_FILE_CLASSPATH + fileName), StandardCharsets.UTF_8).read();
return JSONArray.parseArray(jsonString, clz);
} catch (IOException e) {
throw new RuntimeException("loadJsonAsList got error", e);
}
}
}

View File

@ -0,0 +1,20 @@
package cn.axzo.foundation.unittest.support.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* 如果mysql/h2存在时增加的配置
* 1. 构造DataLoader
*/
@ConditionalOnClass({JdbcTemplate.class})
public class MysqlConfig {
@Bean
public MysqlDataLoader mysqlDataLoader() {
return new MysqlDataLoader();
}
}

View File

@ -0,0 +1,132 @@
package cn.axzo.foundation.unittest.support.config;
import com.google.common.base.Joiner;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.Files;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.ResourceUtils;
import java.io.File;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* mysql 测试数据加载
* <p>
* 测试数据后缀.sql, 默认加载 DEFAULT section 的数据. 如果需要加载指定section的数据需要带上sectionName
*
* <p>
* 文件中通过 "#-->"来定义section. loader可以加载指定section的数据.
* </p>
* <p>
* 文件路径: test/resources/mysql/${testClassName}.sql.
* <p>
* 数据格式如下
* <pre>
* #-->DEFAULT
* sql statement1;
* sql statement2;
*
* #-->section1
* sql statement3;
* sql statement4;
*
* #-->section2
* sql statement5;
* </pre>
*
* @author chenjun
*/
@Slf4j
public class MysqlDataLoader {
private final static String MYSQL_DATA_PATH = "classpath:mysql/";
private final static String MYSQL_FILE_SUFFIX = ".sql";
private final static String DEFAULT_SECTION = "DEFAULT";
@Autowired
private JdbcTemplate jdbcTemplate;
private LoadingCache<File, Multimap<String, String>> sqlFileCaches = CacheBuilder.newBuilder()
.build(new CacheLoader<File, Multimap<String, String>>() {
@SuppressWarnings("NullableProblems")
public Multimap<String, String> load(File file) throws Exception {
List<String> lines = Files.asCharSource(file, Charset.forName("utf-8")).readLines();
String sectionFlag = "#-->";
Multimap<String, String> sections = LinkedListMultimap.create();
String currentSectionName = "";
// 按照section分组.
for (String line : lines) {
line = line.trim();
if (StringUtils.isAllBlank(line)) {
continue;
}
String sectionName = StringUtils.substringAfter(line, sectionFlag);
if (org.apache.logging.log4j.util.Strings.isNotBlank(sectionName)) {
currentSectionName = sectionName;
} else {
sections.put(currentSectionName, line);
}
}
return sections;
}
});
public void loadFromClassName(String className) {
String locationPattern = MYSQL_DATA_PATH + className + MYSQL_FILE_SUFFIX;
File file = getFile(locationPattern);
execStatements(file, ImmutableList.of(DEFAULT_SECTION));
}
public void loadFromClassName(String className, List<String> includeSections) {
String locationPattern = MYSQL_DATA_PATH + className + MYSQL_FILE_SUFFIX;
File file = getFile(locationPattern);
execStatements(file, includeSections);
}
public void load(String filePath) {
String locationPattern = MYSQL_DATA_PATH + filePath;
File file = getFile(locationPattern);
execStatements(file, ImmutableList.of(DEFAULT_SECTION));
}
public void load(String filePath, List<String> includeSections) {
String locationPattern = MYSQL_DATA_PATH + filePath;
File file = getFile(locationPattern);
execStatements(file, includeSections);
}
private File getFile(String locationPattern) {
File file;
try {
file = ResourceUtils.getFile(locationPattern);
} catch (Exception ex) {
throw new RuntimeException("try get file error", ex);
}
return file;
}
private void execStatements(File file, List<String> includeSections) {
Multimap<String, String> sections = sqlFileCaches.getUnchecked(file);
List<String> statements = sections.entries().stream()
.filter(e -> includeSections.isEmpty() || includeSections.contains(e.getKey()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
String sql = Joiner.on("\n").join(statements);
log.info("\n>>>>>>>>>>>>>>> Execute SQL {}\n {}", Joiner.on(",").join(includeSections), sql);
jdbcTemplate.update(sql);
log.info("<<<<<<<<<<<<< Execute DONE\n");
}
}

View File

@ -0,0 +1,31 @@
package cn.axzo.foundation.unittest.support.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.data.redis.core.RedisTemplate;
import redis.embedded.RedisServer;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* redis存在时, 处理相关配置
* 1. 启动embedded的redis
*/
@ConditionalOnClass({RedisTemplate.class})
public class RedisConfig {
private RedisServer redisServer;
@PostConstruct
public void postConstruct() {
redisServer = new RedisServer(16379);
// redis server会在后台启动一个redis server的进程默认IP=127.0.0.1
redisServer.start();
}
@PreDestroy
public void preDestroy() {
// 测试结束后必须调用redisServer.stop(), 否则后台运行的redis server进程会一直存在并占用6379端口
redisServer.stop();
}
}

View File

@ -0,0 +1,17 @@
package cn.axzo.foundation.unittest.support.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
@Slf4j
public class TestConfig {
@Bean
@Primary
public TaskExecutor mockTaskExecutor() {
return new SyncTaskExecutor();
}
}

View File

@ -0,0 +1,45 @@
package cn.axzo.foundation.unittest.support.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
/**
* 构造TransactionManager
* 如果没有mysql/h2时 构造一个空的TransactionManager
*/
@ConditionalOnMissingClass(value = {"org.springframework.jdbc.core.JdbcTemplate"})
public class TransactionalConfig {
@Bean
public TransactionManager transactionManager() {
return new AbstractPlatformTransactionManager() {
@Override
protected Object doGetTransaction() throws TransactionException {
return new Object();
}
@Override
protected void doBegin(Object o, TransactionDefinition transactionDefinition) throws TransactionException {
}
@Override
protected void doCommit(DefaultTransactionStatus defaultTransactionStatus) throws TransactionException {
}
@Override
protected void doRollback(DefaultTransactionStatus defaultTransactionStatus) throws TransactionException {
}
};
}
}

View File

@ -0,0 +1,271 @@
package cn.axzo.foundation.unittest.support.h2;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.google.common.collect.Sets;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 用于支持h2兼容mysql中json的函数
* eg:
* CREATE ALIAS `JSON_EXTRACT` FOR "com.fiture.bfs.unittest.h2.JsonFunctions.extract";
* CREATE ALIAS `JSON_CONTAINS` FOR "com.fiture.bfs.unittest.h2.JsonFunctions.contains";
* CREATE ALIAS `JSON_LENGTH` FOR "com.fiture.bfs.unittest.h2.JsonFunctions.length";
*
* NOTE: JSON.parse(json, Feature.AutoCloseSource)其中Feature.AutoCloseSource是为了兼容fastjson2. 默认该feature在JSON中已经开启
*/
public class JsonFunctions {
/**
* 分割符号
*/
public static final String SEPARATOR = "\\.";
/**
* 根标识符名称
*/
public static final String ROOT_KEY_NAME = "$";
/**
* 数组所有行下标符号
*/
public static final String ARRAY_ANY_INDEX = "*";
/**
* 正则表达式数组匹配模式
*/
public static final Pattern ARRAY_KEY_PATTERN = Pattern.compile("\\[ *(\\*|\\d+) *\\]");
/**
* 根据属性标识从对象中提取数据
*
* @param object 数据对象
* @param key 属性标识
* @return 值对象
*/
private static Object extract(Object object, Key key) {
if (object == null || key == null) {
return object;
} else if (key instanceof PropertyKey) {
String name = ((PropertyKey) key).name;
Object value =
ROOT_KEY_NAME.equals(name) ? object : object instanceof Map ? ((Map<?, ?>) object).get(name) : null;
return extract(value, key.next);
} else if (key instanceof ArrayKey && object instanceof List) {
List<?> list = (List<?>) object;
Integer index = ((ArrayKey) key).index;
return index == null ? list.stream().flatMap(o -> {
Object value = extract(o, key.next);
return value instanceof Collection ? ((Collection) value).stream() : Stream.of(value);
}).filter(Objects::nonNull).collect(Collectors.toList())
: list.size() < index + 1 ? null : extract(list.get(index), key.next);
}
return null;
}
/**
* 根据指定属性标识从JSON数据中抽取数据
*
* @param json JSON数据
* @param key 属性标识
* @return JSON数据结果
*/
public static String extract(String json, String key) {
Object value = extract(JSON.parse(json, Feature.AutoCloseSource), Key.parse(key));
return value == null ? null : convert2str(value);
}
/**
* 结果值转换
* <p>解决字符串toJSON的时候被添加双引号的问题
*
* @param value
* @return
*/
private static String convert2str(Object value) {
if (value instanceof Map || value instanceof Collection) {
return JSON.toJSONString(value);
} else {
return String.valueOf(value);
}
}
/**
* 根据指定属性标识判断值是否在JSON数据中存在
*
* @param json JSON数据
* @param value
* @param key 属性标识
* @return 真假数字true:1false:0
*/
public static int contains(String json, String value, String key) {
if (!StringUtils.isEmpty(json) && !StringUtils.isEmpty(value)) {
Object data = extract(JSON.parse(json, Feature.AutoCloseSource), Key.parse(key));
Object target = JSON.parse(value, Feature.AutoCloseSource);
return targetInJsonData(data, target);
}
return 0;
}
/**
* 根据指定属性标识判断值是否在JSON数据中存在
*
* @param json JSON数据
* @param value
* @return 真假数字true:1false:0
*/
public static int contains(String json, String value) {
if (!StringUtils.isEmpty(json) && !StringUtils.isEmpty(value)) {
Object data = JSON.parse(json, Feature.AutoCloseSource);
Object target = JSON.parse(value, Feature.AutoCloseSource);
return targetInJsonData(data, target);
}
return 0;
}
private static int targetInJsonData(Object data, Object target) {
if (data instanceof List) {
return ((List<?>) data).stream().filter(Objects::nonNull).collect(Collectors.toSet())
.containsAll(target instanceof List
? ((List<?>) target).stream().filter(Objects::nonNull).collect(Collectors.toSet())
: Sets.newHashSet(target)) ? 1 : 0;
} else if (target instanceof List) {
return ((List<?>) target).contains(data) ? 1 : 0;
}
return data != null && target != null && Objects.equals(data, target) ? 1 : 0;
}
/**
* 去除字符串双引号
*
* @param value
* @return
*/
public static String unquote(String value) {
return StringUtils.isEmpty(value) ? value : value.replaceAll("\"", "");
}
/**
* 获取JSON数据长度
*
* @param json JSON字符串
* @return 长度
*/
public static Integer length(String json) {
Object object = JSON.parse(json, Feature.AutoCloseSource);
return object instanceof Map ? ((Map<?, ?>) object).size()
: object instanceof Collection ? ((Collection<?>) object).size() : 0;
}
/**
* 属性标识
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
private static class Key {
/**
* 下一个标识
*/
private Key next;
/**
* 上一个标识
*/
private Key parent;
/**
* 获取跟标识
*
* @return 标识
*/
private Key getRoot() {
return this.parent == null ? this : this.parent.getRoot();
}
/**
* 属性标识链最后追加属性标识
*
* @param key 追加的属性标识
* @return 最后的属性标识
*/
private Key append(Key key) {
Key last = this;
while (last.next != null) {
last = last.next;
}
last.setNext(key);
key.setParent(last);
return key;
}
/**
* 属性标识转换
*
* @param text 属性标识字符串形式
* @return 属性标识对象
*/
public static Key parse(String text) {
Key key = null;
if (!StringUtils.isEmpty(text == null ? null : text.trim())) {
for (String s : text.split(SEPARATOR)) {
String name = ARRAY_KEY_PATTERN.split(s = s.trim())[0];
PropertyKey property = PropertyKey.builder().name(name.trim()).build();
key = key == null ? property : key.append(property);
Matcher matcher = ARRAY_KEY_PATTERN.matcher(s);
while (matcher.find()) {
String group = matcher.group().trim();
String index = group.substring(1, group.length() - 1).trim();
key = key.append(ArrayKey.builder()
.index(ARRAY_ANY_INDEX.equals(index) ? null : Integer.parseInt(index)).build());
}
}
}
return key.getRoot();
}
}
/**
* 字段属性标识
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
private static class PropertyKey extends Key {
/**
* 属性名称
*/
private String name;
}
/**
* 数组属性标识
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
private static class ArrayKey extends Key {
/**
* 数组下标如果为空则表示引用所有记录
*/
private Integer index;
}
}

View File

@ -0,0 +1,23 @@
package cn.axzo.foundation.unittest.support.h2;
import java.util.Arrays;
import java.util.Objects;
/**
* 用于支持h2兼容mysql中string的函数
* eg:
* CREATE ALIAS FIND_IN_SET FOR "com.fiture.bfs.unittest.h2.StringFunctions.findInSet";
*/
public class StringFunctions {
/**
* CREATE ALIAS FIND_IN_SET FOR "com.fiture.bfs.unittest.h2.StringFunctions.findInSet";
*/
public static Integer findInSet(String str, String strList) {
if (!Objects.isNull(str) && !Objects.isNull(strList)) {
return strList.isEmpty() ? 0 : Arrays.asList(strList.split(",")).indexOf(str) + 1;
} else {
return null;
}
}
}