# spring-integrate-mock
**Repository Path**: xgwaw/spring-integrate-mock
## Basic Information
- **Project Name**: spring-integrate-mock
- **Description**: a mock util in Spring context and dependency on junit4/junit5
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2024-03-23
- **Last Updated**: 2024-03-23
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# spring-integrate-mock
a mock util in Spring context and dependency on junit4/junit5
# 功能列表
- [X] 支持junit4/junit5
- [X] junit4支持参数化测试
- [X] 支持普通方法mock
- [X] 支持私有、静态方法mock
- [X] 支持DUBBO调用mock
- [X] 支持Mybatis Mapper调用mock
- [X] 支持调用路径打印(需要代理调用路径上的所有bean)
- [X] 支持单测类/单测方法命名检查(类以Test开头/结尾,方法以test开头)
- [X] 支持真实调用文件采集式mock
# 使用步骤
## 前置依赖
* JDK8
* Junit4/junit5
* Spring4以及以上
* 对象比较 https://github.com/yamikaze0/obj-compare
## 1、依赖引用
* install
```text
git clone https://github.com/yamikaze0/spring-integrate-mock.git
cd spring-integrate-mock && mvn install -DskipTests=true
```
### 1.1、junit4
```xml
org.yamikaze
spring-integrate-junit4
1.0.2
```
### 1.2、junit5
```xml
org.yamikaze
spring-integrate-junit5
1.0.2
```
## 2、Spring Bean配置(必须)
```xml
org.springframework.*.**
```
## 3、使用配置
### 3.1、junit4配置
如果使用Spring自带的runner,还需要在测试类中加入配置
```java
@org.junit.Rule
public org.yamikaze.unittest.junit4.ExtensionRule rule; // 必须声明为public
```
如果不需要上述配置可以使用
```java
@RunWith(org.yamikaze.unittest.junit4.SpringJunitMockRunner.class)
public class XXXTest {}
```
`SpringJunitMockRunner`会自动将`rule`生效
### 3.2、junit5配置
junit配置比较简单,只需要在单测类上加入如下注解配置即可
```java
@ExtendWith(Junit5MethodEachCallback.class)
public class XXXTest {}
```
### 3.3、配置代理的bean列表(必须)
经过上述配置之后,还需要配置需要代理那些bean,支持多维度的bean配置,我们需要在Spring容器初始化前配置,所以
一般在junit4中的`@BeforeClass`或者junit5中的`@BeforeAll`注解修饰的方法中进行配置,默认情况下,自动代理DUBBO reference bean,但如果使用注解@Reference来引用Dubbo服务,则不会生效(因为未在Spring容器内产生bean定义)。
,以下为配置示例
* 针对beanName的配置
```java
// 某些bean需要被代理
GlobalConfig.proxy().includeBean("*Adapter*");
// 某些bean不想被代理
GlobalConfig.proxy().excludeBean("*Client*");
```
* 针对classType的mock配置
```java
// 需要对某个class进行mock代理
GlobalConfig.proxy().includeType(UserService.class);
// 需要对某些class的通配符进行代理
GlobalConfig.proxy().includeType("com.xxx.xxx.xxx.*.**");
// 不想要对某个class进行mock
GlobalConfig.proxy().excludeType(UserService.class);
// 不想要对一类class进行代理
GlobalConfig.proxy().excludeType("com.xxx.xxx.xxx.*.**");
```
* 针对classType必须使用jdk代理
默认情况下,会对Mybatis Mapper,Dubbo的ReferenceBean使用jdk代理
```java
GlobalConfig.proxy().includeJdkProxy(UserService.class);
// 不想使用jdkProxy
GlobalConfig.proxy().excludeJdkProxy(UserApi.class);
```
* 不进行mock代理
```java
GlobalConfig.proxy().disableProxy();
// 不对dubbo进行代理
GlobalConfig.proxy().disableDubboProxy();
```
### 3.4、其他配置
* 开启/关闭mock
```java
GlobalConfig.setMockEnabled(true);
```
* 打印真实调用日志
当发生真实调用时会打印此日志
```java
GlobalConfig.setEnableRealInvokeLog(true);
```
* 设置代理方式 cglib/java-native
```java
GlobalConfig.setUseCglibProxy(true/false);
```
* 启用/禁用agent
在Junit4下,如果禁用agent,则会使用classloader进行字节码修改(classloader方式存在诸多问题,依赖于类加载器的传递性,需要额外配置才可能生效),Junit5下禁用agent,则静态、私有方法mock会失效
```java
GlobalConfig.setUseAgentProxy(true);
```
## 4、使用示例
### 4.1、使用注解形式
```java
// step1 使用@Mock注解声明方法的key
@Mock(clz = UserService.class, method = "getUser", key = "mock-getUser")
@Test
public void testUser() {
// step2: 使用DataCodeFactory注册key对应的返回值,注入的值可以是异常对象
User expect = new User();
DataCodeFactory.register("mock-getUser", expect);
// step3、userApi调用userService
User u = userApi.getUser(1L);
assert u == expect;
}
```
### 4.2、mock-record形式
使用类似easymock和mockit的形式
```java
@Test
public void testGetUser() {
// step1: 使用MockUtils获取代理对象,并进行mock-record
UserMapper mock = MockUtils.createMock(UserMapper.class);
User expect = JSON.parseObject("{\"password\":\"123456\",\"salt\":\"hel\",\"phone\":\"13467830023\",\"id\":1,\"username\":\"4088586803\"}\n", User.class);
// when(T, boolean)的重载表示是否进行参数匹配,如果false或者默认方法,不会进行参数匹配
MockUtils.when(mock.selectById(1L), true).matchBean("xxxService").thenReturn(expect);
// step2: 调用userApi
User actual = userApi.getUser(1L);
assert actual == expect;
}
```
### 4.3、文件采集形式
step1: 文件采集形式需要引入pom
```xml
org.yamikaze
spring-integrate-mock-file
1.0.1
```
step2: 指定哪些接口需要采集调用结果
```java
@Test
public void testGetUser() {
// 指定哪些接口需要采集,ant通配符形式
// mock(true)表示如果之前有采集的文件,会走mock的形式
Mockit.mock(true).mock(Arrays.asList(UserService.class.getName() + "*"));
User actual = userApi.getUser(1L);
System.out.println(actual.getId());
}
```
Tip: 文件采集的方式第一次运行会进行真实调用,如果真实调用的接口和方法能够匹配上指定的列表,会将调用
的参数以及返回值以json文件的形式保存下来,默认保存在当前工作目录/mock下,文件路径为当前测试类的全称限定名+方法名,
你也可以使用`GlobalConfig.setSaveResourceLocation(dir)`进行重新设置
Tip2: 默认情况下,采集到的参数在进行mock时,会进行参数匹配,如果想要关闭匹配,可以使用`Mockit.mock(true).matchParams(false)`关闭方法全局参数匹配,或者进入到采集的文件,给json
加上 matchParams = false的键值对
### 4.4、静态方法mock
step1: 在单测方法上指定需要对哪些方法进行增强
```java
@MockEnhance(RandomUtils.class)
public class XXXTest{}
```
step2: 在单测方法内完成录制
```java
// 表示对RandomUtils#randomString进行mock,方法参数为int,值为10,返回987654321
MockUtils.mock(RandomUtils.class).mockMethod("randomString")
.types(int.class).param(10).result("987654321");
```
## 5、其他功能
### 5.1、调用路径打印
默认情况下,会对所有代理的bean以及静态mock的方法调用进行采集,然后打印,效果如下:
```text
2022-06-15 15:45:50.585 INFO InvokeTree [InvokeTree.java:109] - Invoke Tree Dump:
org.yamikaze.spring.mock.example.ApplicationTest#testGetUser time = 189ms, mode = mix
|---UserApi#getUser time = 37ms, mode = mix
|---UserService#getUser time = 22ms, mode = mock
| |---UserMapper#selectById time = 17ms, mode = mock
| |---org.yamikaze.spring.mock.example.RandomUtils#randomString time = 0ms, mode = mock
|---UserLogService#log time = 4ms, mode = real-invoke
```