0%

SpringBoot-Test-Mock

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

  • 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) //触发时第一次返回value1,第n次都返回value2
Mockito.doThrow(toBeThrown).when(mock).[method] //模拟抛出异常。
Mockito.mock(classToMock,defaultAnswer) //使用默认Answer模拟对象
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) //重置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

/**
* An enumeration web environment modes.
*/
enum WebEnvironment {

/**
* 加载 WebApplicationContext 并提供一个 Mock 的 Servlet 环境,此时内置的 Servlet 容器并没有正式启动。
*/
MOCK(false),

/**
* 加载 EmbeddedWebApplicationContext 并提供一个真实的 Servlet 环境,然后使用一个随机端口启动内置容器。
*/
RANDOM_PORT(true),

/**
* 这个配置也是通过加载 EmbeddedWebApplicationContext 提供一个真实的 Servlet 环境,但使用的是默认端口,如果没有配置端口就使用 8080。
*/
DEFINED_PORT(true),

/**
* 加载 ApplicationContext 但并不提供任何真实的 Servlet 环境。
*/
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);
}
}

}
  • Console
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");
}

}
  • Console
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