搭建SpringBoot MockMvc测试环境,并用mvn test运行测试

204 阅读4分钟

搭建SpringBoot MockMvc测试环境,并用mvn test运行测试

基于Controller层的测试框架,通过代码的方式来模拟HTTP请求, MockMvc是Spring MVC测试的一个类,它提供了一种在测试环境下模拟发送HTTP请求和验证返回结果的方式。使用MockMvc,可以对Controller的行为进行测试,而无需启动Web服务器。

MockMvc的使用步骤如下:

  1. 创建MockMvc实例。可以通过使用MockMvcBuilders的静态方法,如MockMvcBuilders.standaloneSetup(controller)来创建MockMvc实例。也可以使用@AutoConfigureMockMvc注解来自动注入MockMvc实例。

  2. 构建和配置Mock请求。使用MockMvc的perform()方法来构建Mock请求。可以通过调用MockMvcRequestBuilders的静态方法,如get()post()等构建不同的HTTP请求。然后可以通过链式调用param()header()等方法来设置请求参数和头部信息。

  3. 发送Mock请求。通过调用MockMvc的perform()方法,传入前面构建的Mock请求,来发送模拟的HTTP请求。

  4. 验证返回结果。通过调用返回的MvcResult对象的方法,如getResponse()getModelAndView()等来获取请求的响应结果或模型视图。然后可以进行结果的断言、验证和处理。

目录结构

image-20230412141108448

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;
    }
}

创建测试代码

image-20230412143712758

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);
    }
}

image-20230412144642892

再通过mvn test 进行测试

image-20230412145039389

image-20230412145411597

到这里已经大功告成,通过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…