# 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