# spring_test_demo
**Repository Path**: apo-soft/spring_test_demo
## Basic Information
- **Project Name**: spring_test_demo
- **Description**: 初始地址:https://github.com/zk4/spring_test_demo.git
绝大部分自学习的mockito技术和实际使用方法
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2024-01-31
- **Last Updated**: 2024-07-18
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 使用Mockito和Spring-test执行单元测试
## Mockito文档参考连接
[Mockito 官方网站](https://site.mockito.org/)
[Mockito Document网站](https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html)
[本项目初始教程地址(github)](https://github.com/zk4/spring_test_demo.git)
[源码地址](https://gitee.com/apo-soft/spring_test_demo)
``` java
/**
* @see org.mockito.Mockito
* @see org.mockito.BDDMockito
*/
```
## 单元测试项目准备
### 关键版本说明:
"mockito-inline"模式,自2.7.6版本引入,自5版本被设置为默认模式。
"mock-static"机制,自3.4.0版本引入
### 引入依赖(本案例采用Junit 4作为测试引擎):
注意,当“spring-boot-starter-test”的版本较高,引入了Junit-jupiter时,需要额外排除。
也可以采用Junit-jupiter作为测试引擎,需要采用另外一套注解和Assert语法。
``` xml
junit
junit
4.13.1
test
org.springframework.boot
spring-boot-starter-test
test
org.junit.jupiter
junit-jupiter
org.mockito
mockito-junit-jupiter
org.hamcrest
java-hamcrest
2.0.0.0
test
```
### 启用mock-in-line
该机制在mockito-5系列已经作为默认机制启用,但目前spring-boot-2系列普遍依赖版本2~4,需要手动配置启用
为兼容静态注解等机制,Mockito尽量采用主流的4系列版本,2系列版本在关键功能上有缺失。
为什么要启用mock-in-line?
主要解决在早期继承模式下无法实现的 **_final_** 修饰方法无法被mock的问题,利用inline模式,可以从方法内对代码进行增强,而无需继承。
``` jsonpath
--test
----resources
------mockitoextensions
--------org.mockito.plugins.MockMaker
内容: mock-maker-inline
```
## 测试案例
### 案例1: Controller层单元测试案例
#### 1. 模拟http服务器请求的单测案例
测试类: com.zk.MyController_1_Test
关键点:
MockMvcBuilders
given语法要素
#### 2. 利用SpringRunner加载 Spring TestContext Framework
测试类: com.zk.MyController_2_Test
关键点:
``` java
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
TestRestTemplate restTemplate;
```
#### 3. 利用 @AutoConfigureMockMvc 自动注入 MockMvc,与SpringRunner配合使用
测试类: com.zk.MyController_3_Test
关键点
@AutoConfigureMockMvc 自动注入MockMvn
#### 3. 利用 @AutoConfigureMockMvc 自动注入 MockMvc,与SpringRunner配合使用
测试类: com.zk.MyController_4_Test
关键点:
``` java
@WebMvcTest(UserController.class)
@MockBean
private UserService userService;
```
### 案例2: Service层单元测试案例
#### 1. 常规的Service测试案例,final方法的测试用例
测试类: com.zk.service.UserServiceTest
关键点:
使用教程开始的mock-in-line配制来实现方法内代码增强,规避java无法对final修饰进行重载的限制
#### 2. 利用现有对象注入 @SPY
测试类: com.zk.service.UserServiceTest_2
关键点: 利用@Spy实现对现有真实功能的利用。注意: 需要对对象进行实例化,不能依赖自动生成机制。
#### 3. 方法内静态方法 **_static_** 方法mock方法
测试类: com.zk.service.BookServiceTest
参考: 官方文档48. Mocking static methods (since 3.4.0)
关键点: 采用合适的Mockito版本,使用**_try(){}_**域限定静态方法存根(stub)作用范围。
举例:
``` java
/**
* @see org.mockito.MockedStatic.Verification
*/
assertEquals("foo", Foo.method());
try (MockedStatic mocked = mockStatic(Foo.class)) {
mocked.when(Foo::method).thenReturn("bar");
assertEquals("bar", Foo.method());
mocked.verify(Foo::method);
}
assertEquals("foo", Foo.method());
```
### 案例3: 入参的赋值和比对方法
#### 1. Mock参调用方法存根的常见方法举例,存根(stub)使用方法
测试类: com.zk.service.CarServiceTest
关键点: 使用 **_@RunWith(MockitoJUnitRunner.class)_** 修饰测试类型, ArgumentMatchers的使用方法,any,eq,等常用动词的使用方法
常见的 given,when等参数条件关键词使用方法。
参考: org.mockito.ArgumentMatchers
#### 2. 自定义参数比对规则,应对未重载equals方法的对象参数
测试类: com.zk.service.PlaneServiceTest
关键点: 使用argThat来自定match方法
#### 3. 一个复杂场景案例,如何覆盖传递到mock类型中的函数式对象的代码
测试类: cn.aposoft.search.es.portal.GeneralSearchPortalTest
测试类: com.zk.mockito.cases.Case015_CapturingArgumentsForFurtherAssertions
测试类: com.zk.mockito.cases.Case017_VerificationWithAssertions
Since 5.3:最新的参数验证方法 ArgumentMatchers.assertArg
55. Verification with assertions (Since 5.3.0) ArgumentMatchers.assertArg(Consumer)
To validate arguments during verification, instead of capturing them with ArgumentCaptor, you can now use ArgumentMatchers.assertArg(Consumer)}:
```java
verify(serviceMock).doStuff(assertArg(param -> {
assertThat(param.getField1()).isEqualTo("foo");
assertThat(param.getField2()).isEqualTo("bar");
}));
```
要点:
在执行过程中,找到mock节点的执行方法,驱动该对象的执行,详情参考代码示例。
利用BDDMockito的when...then...机制实现对匿名函数式方法的捕获和执行。
利用 ArgumentCaptor argument = ArgumentCaptor.forClass(Person.class); 可以实现对参数的捕获,参考上述测试案例
it is recommended to use ArgumentCaptor with verification but not with stubbing.
#### 4. void 方法抛出异常的mock
测试类: com.zk.service.ListMockExceptionTest
举例: doThrow(new RuntimeException()).when(mockedList).clear();
@see org.junit.function.ThrowingRunnable
#### 5. 多参数存根
测试类:
要点: 多参数存根的每一个参数都使用一个ArgumentMatchers静态方法修饰,如any,eq等。
Warning on argument matchers:
If you are using argument matchers, all arguments have to be provided by matchers.
### 案例4: 验证入门
#### 1. verify 入门
测试类: Case001_VerifyTest
验证某个行为确定发生了一次(或多次)
Verifies certain behavior happened once.
Alias to verify(mock, times(1)) E.g: verify(mock).someMethod("some arg");
#### 2. stubbing 保存存根
测试类: Case002_StubTest
要点: 针对mocked类型,记录其在接收特定输入条件时的应返回值。
调用方法的入参,可以是直接值,也可以是ArgumentMatchers的方法修饰的值
直接对象比对和eq方法修饰,默认采用Object.equals方法进行比对
#### 3. Argument Matchers 比对方法
测试类: Case003_ArgumentMatchersTest
常见的入参传入方法和值替代方法,需要看文档掌握。
多参数值时,需要使用ArgumentMatcher修饰
#### 4. Spy on real Object
测试类: Case005_SpyingOnRealObjectsTest
要点: 使用doReturn等做stub存根
List list = new LinkedList();
List spy = spy(list);
//Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");
//You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);
#### 5. static 属性
测试包: com.zk.mockito.cases.case006.ConcreteHolder1Test
注意点:
1. 每次使用前对静态属性进行赋值,在使用后还原赋值。
2. 设计用于测试的静态属性类型的继承类,并实现简单的符合测试目的的逻辑。
3. 尽量避免对static属性的直接操作,如果需要,将对静态属性的操作封装在一个静态方法中,并对静态方法进行mockStatic
4. 尽量避免测试对代码的更改造成全局性污染,这是单元测试的大忌。
5. 使用反射技术读取静态属性进行处理,处理完成后需要还原。
#### 6. Do Not Mock
测试类: com.zk.mockito.cases.Case000_DoNotMocks
关键点: 注解: @DoNotMock(reason = "Just as an example.")
@DoNotMock 本注解修饰的是被Mock的目标类型,而不是测试类型。
* 当某个类被注解为@DoNotMock时, 如果其自身被Mock会抛出异常如下:
* org.mockito.exceptions.misusing.DoNotMockException:
* class com.zk.mockito.cases.Case000_DoNotMocks$Bike
* is annotated with @org.mockito.DoNotMock and can't be mocked. Just as an example.
/**
* 单车
*/
@DoNotMock(reason = "Just as an example.")
static class Bike {
private String name = "John";
public Bike() {
}
public Bike(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
#### 6. Constructor Mock
测试类: com.zk.mockito.cases.Case016_MockedConstruction