SpringBoot测试解决方案
mock
- Mock 的意思是模拟,它可以用来对系统、组件或类进行隔离。
- 验证组件级别正确性的一大难点在于关于组件与组件之间的依赖关系,这里就需要引出测试领域非常重要的一个概念,即 Mock(模拟)。
mvc-mock 测试Controller
service-mock 测试 Service
repository-mock 测试 Data
remote-mock 测试 远程接口
Spring-Boot-Start-Test
Spring Test & Spring Boot Test:为 Spring 和 Spring Boot 框架提供的测试工具。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| [INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.4.5:test [INFO] | +- org.springframework.boot:spring-boot-test:jar:2.4.5:test [INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.4.5:test [INFO] | +- com.jayway.jsonpath:json-path:jar:2.4.0:test [INFO] | | \- net.minidev:json-smart:jar:2.3:test [INFO] | | \- net.minidev:accessors-smart:jar:1.2:test [INFO] | | \- org.ow2.asm:asm:jar:5.0.4:test [INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test [INFO] | | \- jakarta.activation:jakarta.activation-api:jar:1.2.2:test [INFO] | +- org.assertj:assertj-core:jar:3.18.1:test [INFO] | +- org.hamcrest:hamcrest:jar:2.2:test [INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.7.1:test [INFO] | | \- org.junit.jupiter:junit-jupiter-params:jar:5.7.1:test [INFO] | +- org.mockito:mockito-core:jar:3.6.28:test [INFO] | | +- net.bytebuddy:byte-buddy:jar:1.10.22:test [INFO] | | +- net.bytebuddy:byte-buddy-agent:jar:1.10.22:test [INFO] | | \- org.objenesis:objenesis:jar:3.1:test [INFO] | +- org.mockito:mockito-junit-jupiter:jar:3.6.28:test [INFO] | +- org.skyscreamer:jsonassert:jar:1.5.0:test [INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test [INFO] | +- org.springframework:spring-test:jar:5.3.6:test [INFO] | \- org.xmlunit:xmlunit-core:jar:2.7.0:test [INFO] +- org.junit.jupiter:junit-jupiter-api:jar:5.7.1:test [INFO] | +- org.apiguardian:apiguardian-api:jar:1.1.0:test [INFO] | +- org.opentest4j:opentest4j:jar:1.2.0:test [INFO] | \- org.junit.platform:junit-platform-commons:jar:1.7.1:test [INFO] \- org.junit.jupiter:junit-jupiter-engine:jar:5.7.1:test [INFO] \- org.junit.platform:junit-platform-engine:jar:1.7.1:test
|
- JUnit:JUnit 是一款非常流行的基于 Java 语言的单元测试框架,在我们的课程中主要使用该框架作为基础的测试框架。
- JSON Path:类似于 XPath 在 XML 文档中的定位,JSON Path 表达式通常用来检索路径或设置 JSON 文件中的数据。
- AssertJ:AssertJ 是一款强大的流式断言工具,它需要遵守 3A 核心原则,即 Arrange(初始化测试对象或准备测试数据)——> Actor(调用被测方法)——>Assert(执行断言)。
- Mockito:Mockito 是 Java 世界中一款流行的 Mock 测试框架,它主要使用简洁的 API 实现模拟操作。在实施集成测试时,我们将大量使用到这个框架。
- Hamcrest:Hamcrest 提供了一套匹配器(Matcher),其中每个匹配器的设计用于执行特定的比较操作。
- JSONassert:JSONassert 是一款专门针对 JSON 提供的断言框架。
mock框架
- 这里推荐使用mockito 和 阿里开源的TestableMock。
- 使用spring boot start test 会默认引入mockito
1 2 3 4 5 6 7 8 9 10 11
| Mockito.mock(classToMock) Mockito.verify(mock) Mockito.when(methodCall).thenReturn(value1).thenReturn(value2) Mockito.doThrow(toBeThrown).when(mock).[method] Mockito.mock(classToMock,defaultAnswer) Mockito.when(methodCall).thenReturn(value) Mockito.doReturn(toBeReturned).when(mock).[method] Mockito.when(methodCall).thenAnswer(answer)) Mockito.doAnswer(answer).when(methodCall).[method] Mockito.doNothing().when(mock).[method] reset(mock)
|
SpringBoot-mock
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Slf4j @ExtendWith(SpringExtension.class) @SpringBootTest(classes = AppApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) public class AppApplicationTest {
@Autowired private ApplicationContext applicationContext;
@Test public void testContext() { Assertions.assertThat(applicationContext).isNotNull(); } }
|
- @ExtendWith(SpringExtension.class) 是junit5的注入方式。
- @SpringBootTest 注解,SpringBoot 专门提供了一个 @SpringBootTest 注解测试 Bootstrap 类。同时 @SpringBootTest 注解也可以引用 Bootstrap 类的配置,因为所有配置都会通过 Bootstrap 类去加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
enum WebEnvironment {
MOCK(false),
RANDOM_PORT(true),
DEFINED_PORT(true),
NONE(false); }
|
Controller-mock
- 接口测试的时候mock controller层,并开启http端口
- 引入@AutoConfigureMockMvc
- 请注意 @SpringBootTest 注解不能和 @WebMvcTest 注解同时使用。
- MockMvc提供部分方法
- Perform:执行一个 RequestBuilder 请求,会自动执行 SpringMVC 流程并映射到相应的 Controller 进行处理。
- get/post/put/delete:声明发送一个 HTTP 请求的方式,根据 URI 模板和 URI 变量值得到一个 HTTP 请求,支持 GET、POST、PUT、DELETE 等 HTTP 方法。
- param:添加请求参数,发送 JSON 数据时将不能使用这种方式,而应该采用 @ResponseBody 注解。
- andExpect:添加 ResultMatcher 验证规则,通过对返回的数据进行判断来验证 Controller 执行结果是否正确。
- andDo:添加 ResultHandler 结果处理器,比如调试时打印结果到控制台。
- andReturn:最后返回相应的 MvcResult,然后执行自定义验证或做异步处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @Slf4j @ExtendWith(SpringExtension.class) @SpringBootTest(classes = AppApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureWebFlux public class AppControllerTest {
@Autowired private WebTestClient webTestClient;
@MockBean private AppController appController;
@Test public void testVersion() throws Exception { Map<String, AppVo> testData = new HashMap<>(); testData.put("undefined", new AppVo("", "undefined")); testData.put("mysql", new AppVo("mysql", "1.0.0")); testData.put("redis", new AppVo("redis", "2.0.0")); for (String s : testData.keySet()) { Mockito.when(appController.version(s)).thenReturn(testData.get(s)); EntityExchangeResult<String> result = webTestClient.post() .uri("/" + s) .contentType(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk() .expectHeader().contentType(MediaType.APPLICATION_JSON) .expectBody(String.class) .returnResult(); log.info("{}",result); } }
}
|
1 2 3
| [main] {"key":"mysql","version":"1.0.0"} [main] {"key":"redis","version":"2.0.0"} [main] {"key":"","version":"undefined"}
|
Service-mock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Slf4j @ExtendWith(SpringExtension.class) @SpringBootTest(classes = AppApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) public class AppServiceITest {
@MockBean private AppServiceI appService;
@Test public void testVersion() { String key = "mysql"; Mockito.when(appService.version("")).thenReturn(new AppVo("", "undefined")); Mockito.when(appService.version("mysql")).thenReturn(new AppVo("mysql", "5.7.36")); Mockito.when(appService.version("redis")).thenReturn(new AppVo("redis", "6.2.6")); AppVo appVo = appService.version(key); Assertions.assertThat(appVo).isNotNull(); log.info("{}",JsonTool.toString(appVo)); Assertions.assertThat(appVo.getVersion()).isEqualTo("5.7.36"); appVo = appService.version("redis"); Assertions.assertThat(appVo).isNotNull(); log.info("{}",JsonTool.toString(appVo)); Assertions.assertThat(appVo.getVersion()).isEqualTo("6.2.6"); appVo = appService.version(""); Assertions.assertThat(appVo).isNotNull(); log.info("{}",JsonTool.toString(appVo)); Assertions.assertThat(appVo.getVersion()).isEqualTo("undefined"); }
}
|
1 2 3
| [main] {"key":"mysql","version":"5.7.36"} [main] {"key":"redis","version":"6.2.6"} [main] {"key":"","version":"undefined"}
|
Remote-mock
- remote-mock 测试 远程接口在spring boot cloud test mock中演示。
Data-mock
日常开发一般都会使用持久层,引入 DataJdbcTest 注解进行mock测试。如果是mybaits可以使用@MybatisTest、如果是MybatisPuls可以使用@MybatisPulsTest。只需要分别引入依赖即可。
- 演示项目用的jdbc,平时用的是mybaits,MybaitsTest单独写一篇文档。
END