搭建SpringBoot MockMvc测试环境,并用mvn test运行测试
基于Controller层的测试框架,通过代码的方式来模拟HTTP请求, MockMvc是Spring MVC测试的一个类,它提供了一种在测试环境下模拟发送HTTP请求和验证返回结果的方式。使用MockMvc,可以对Controller的行为进行测试,而无需启动Web服务器。
MockMvc的使用步骤如下:
-
创建MockMvc实例。可以通过使用MockMvcBuilders的静态方法,如
MockMvcBuilders.standaloneSetup(controller)来创建MockMvc实例。也可以使用@AutoConfigureMockMvc注解来自动注入MockMvc实例。 -
构建和配置Mock请求。使用MockMvc的
perform()方法来构建Mock请求。可以通过调用MockMvcRequestBuilders的静态方法,如get()、post()等构建不同的HTTP请求。然后可以通过链式调用param()、header()等方法来设置请求参数和头部信息。 -
发送Mock请求。通过调用MockMvc的
perform()方法,传入前面构建的Mock请求,来发送模拟的HTTP请求。 -
验证返回结果。通过调用返回的
MvcResult对象的方法,如getResponse()、getModelAndView()等来获取请求的响应结果或模型视图。然后可以进行结果的断言、验证和处理。
目录结构
maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>absra-2023</artifactId>
<groupId>com.absra</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>absra-mockmvc</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!-- test engine 后面测试没有也可以成功执行mvn test -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<!-- 构建jar文件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Controller层代码
package com.absra.controller;
import com.absra.entity.Student;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* @author absra
* @date 11/04/2023
* @apiNote
**/
@RequestMapping("/api/student")
@Slf4j
@RestController
public class MockMvcController {
@GetMapping("/get/{stuNo}")
public Object get(@PathVariable String stuNo) {
log.info("get() called with parameters => 【stuNo = {}】",stuNo);
return new Student(1, "absra", "2 classes", "nan", "giabsra@outlook.com");
}
@PostMapping("/post")
public Object post(@RequestBody Student student) {
log.info("post() called with parameters => 【student = {}】",student);
return student;
}
@GetMapping("/header")
public Object header(@RequestHeader(name = "userToken") String userToken) {
log.info("header() called with parameters => 【userToken = {}】",userToken);
return userToken;
}
}
创建测试代码
package com.absra.controller;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author absra
* @date 12/04/2023
* @apiNote
**/
class MockMvcControllerTest {
@Test
void get() {
}
@Test
void post() {
}
@Test
void header() {
}
}
修改测试类代码,并添加Mock用于测试Controller层中的方法
package com.absra.controller;
import com.absra.MockMvcApplication;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;
import java.nio.charset.StandardCharsets;
/**
* @author absra
* @date 12/04/2023
* @apiNote
**/
@Slf4j
@SpringBootTest(classes = MockMvcApplication.class)
class MockMvcControllerTest {
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
private HttpHeaders headers;
private static final String baseUrl = "/api/student/";
@BeforeEach
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
MultiValueMap<String, String> headerMap = new LinkedMultiValueMap<>();
headers = new HttpHeaders();
headerMap.add("userToken","token123");
headers.addAll(headerMap);
}
@Test
void get() throws Exception {
log.info("get() called with parameters => ------------------");
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(baseUrl + "get/123")
.headers(headers)
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().json("{\"id\":1,\"name\":\"absra\",\"classes\":\"2 classes\",\"gender\":\"nan\",\"email\":\"giabsra@outlook.com\"} "))
.andReturn();
String contentAsString = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
log.info("get() called with parameters => {} ",contentAsString);
}
@SneakyThrows /* SneakyThrows注解就是减少了代码量,让代码看起来更加的整洁。*/
@Test
void post() {
log.info("post() called with parameters => ");
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post(baseUrl + "post")
.headers(headers)
.content("{\"id\":1,\"name\":\"absra\",\"classes\":\"23 classes\",\"gender\":\"nan\",\"email\":\"giabsra@outlook.com\"}")
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String contentAsString = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
log.info("get() called with parameters => {} ",contentAsString);
}
@SneakyThrows
@Test
void header() {
log.info("header() called with parameters => header ------------- ");
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(baseUrl + "header")
.headers(headers)
.content("{\"id\":1,\"name\":\"absra\",\"classes\":\"23 classes\",\"gender\":\"nan\",\"email\":\"giabsra@outlook.com\"}")
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String contentAsString = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
log.info("header() returned: {}" ,contentAsString);
}
}
再通过mvn test 进行测试
到这里已经大功告成,通过mvn test全部测试方法都执行到了
回头来聊聊 MockMvc这个框架
我们先通过get这个测试方法进行分解
void get() throws Exception {
log.info("get() called with parameters => ------------------");
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(baseUrl + "get/123")
.headers(headers)
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().json("{\"id\":1,\"name\":\"absra\",\"classes\":\"2 classes\",\"gender\":\"nan\",\"email\":\"giabsra@outlook.com\"} "))
.andReturn();
String contentAsString = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
log.info("get() called with parameters => {} ",contentAsString);
}
| 方法 | 作用 |
|---|---|
| mockMvc.perform() | 执行一个请求操作 |
| MockMvcRequestBuilders.get(baseUrl + "get/123") | 构造一个GET请求 |
| .headers(headers) | 把上面的HttpHeaders类添加到构造的请求体重 |
| .contentType(MediaType.APPLICATION_JSON) | 请求体中的信息用JSON格式传递 |
| 请求体构建信息构建完毕,接下来做断言 | |
| .andDo(MockMvcResultHandlers.print()) | 添加一个结果处理器,打印请求体相关信息 |
| .andExpect(MockMvcResultMatchers.status().isOk()) | 判断状态是否为200 |
| .andExpect(MockMvcResultMatchers.content().json() | 判断响应结果与断言结果一致 |
| 断言信息构建完毕 | |
| .andReturn(); | 执行后返回响应的结果信息,MvcResult |
| result.getResponse().getContentAsString(); | 获取响应体中的信息并转换成string格式,contentAsString |
其他方法以此类推,也可以直接通过 ResultActions 类源码,来获取更多细节信息
知识点提炼
@AutoConfigureMockMvc
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
@BeforeEach
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
MultiValueMap<String, String> headerMap = new LinkedMultiValueMap<>();
headers = new HttpHeaders();
headerMap.add("userToken","token123");
headers.addAll(headerMap);
}
构建MockMvc基础信息时,可以直接用@AutoConfigureMockMvc代替,也可以自己构建
@AutoConfigureMockMvc // 自动配置MockMvc
class StudentControllerTest {
@Resource
private MockMvc mockMvc;
}
@SneakyThrows
使用该注解后不需要担心Exception的异常处理,SneakyThrows注解就是减少了代码量,让代码看起来更加的整洁。
import lombok.SneakyThrows;
maven-surefire-plugin
参考地址;【Maven】maven 插件 maven-surefire-plugin_surefire plugin_九师兄的博客-CSDN博客
maven 本身不是一个单元测试框架,他知识在构建执行到特定生命周期阶段的时候,通过插件来行JUnit 或者 TestNG的测试用例
这个maven-surefire-plugin,也可以称为测试运行器
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<includes>**/*</includes> <!-- 执行src/test/java 路径下的的所有文件,** 为不限制目录层级,例子;**/*.java-->
<excludes>不希望被执行到的文件</excludes>
</configuration>
</plugin>
@BeforeEach
在每个测试方式执行前都加上统一的附加信息
@BeforeEach
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
MultiValueMap<String, String> headerMap = new LinkedMultiValueMap<>();
headers = new HttpHeaders();
headerMap.add("userToken","token123");
headers.addAll(headerMap);
}
代码地址
Gitee;gitee.com/absra-artic…