mockmvc技术分享

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 对模块进行集成测试时,希望能够通过输入URL对Controller进行测试,如果通过启动服务器,建立http client进行测试,这样会使得测试变得很麻烦,比如,启动速度慢,测试验证不方便,依赖网络环境等MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便

简介


对模块进行集成测试时,希望能够通过输入URL对Controller进行测试,如果通过启动服务器,建立http client进行测试,这样会使得测试变得很麻烦,比如,启动速度慢,测试验证不方便,依赖网络环境等


MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便


流程


  1. MockMvcBuilder实例化MockMvc
  2. mockMvc调用perform,执行一个RequestBuilder请求,调用controller的业务处理逻辑
  3. perform返回ResultActions,返回操作结果,通过ResultActions,提供了统一的验证方式
  4. 使用StatusResultMatchers对请求结果进行验证
  5. 使用ContentResultMatchers对请求返回的内容进行验证


代码


这里是完整的测试案例:包含Junit5以及Mockmvc


package com.uncle.junittest.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
 * @author 
 * @date 2022-01-27 上午10:30
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    /**
     * 序列号
     */
    private static final long serialVersionUID = -4449140937612137858L;
    /**
     * 用户id
     */
    private String id;
    /**
     * 昵称
     */
    private String fullName;
    /**
     * 用户名
     */
    private String userName;
    /**
     * 密码
     */
    private String password;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 性别
     */
    private String sex;
}


package com.uncle.junittest.mapper;
import com.uncle.junittest.bean.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
/**
 * @author 
 * @date 2022-01-27 下午3:30
 **/
@Repository
public interface UserMapper {
    /**
     * 增加用户的方法
     *
     * @param user 用户信息
     * @return 增加成功返回:1;否则返回:0
     */
    @Insert("insert into user values (#{id},#{fullName},#{userName},#{password},#{age},#{sex})")
    int saveUser(User user);
    /**
     * 根据id查询用户信息
     *
     * @param id 用户id
     * @return 用户信息
     */
    @Select("select * from user where id = #{id}")
    User getUser(String id);
}


package com.uncle.junittest.service;
import com.uncle.junittest.bean.User;
import org.apache.ibatis.annotations.Select;
/**
 * @author 
 * @date 2022-01-27 下午3:29
 **/
public interface UserService {
    /**
     * 增加用户的方法
     * @param user 用户信息
     * @return 增加成功返回:1;否则返回:0
     */
    int saveUser(User user);
    /**
     * 根据id查询用户信息
     * @param id 用户id
     * @return 用户信息
     */
    User getUser(String id);
}
package com.uncle.junittest.service.impl;
import com.uncle.junittest.bean.User;
import com.uncle.junittest.mapper.UserMapper;
import com.uncle.junittest.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * @author 
 * @date 2022-01-27 下午3:29
 **/
@Service
public class UserServiceImpl implements UserService {
    /**
     * UserMapper对象
     */
    @Autowired
    private UserMapper userMapper;
    /**
     * 增加用户的方法
     * @param user 用户信息
     * @return 增加成功返回:1;否则返回:0
     */
    @Override
    public int saveUser(User user) { return userMapper.saveUser(user); }
    /**
     * 根据id查询用户信息
     * @param id 用户id
     * @return 用户信息
     */
    @Override
    public User getUser(String id) {
        return userMapper.getUser(id);
    }
}


package com.uncle.junittest.controller;
import com.uncle.junittest.bean.User;
import com.uncle.junittest.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
 * @author 
 * @date 2022-01-27 下午3:29
 **/
@RequestMapping("/test")
@RestController
public class UserController {
    /**
     * UserService对象
     */
    @Autowired
    private UserService userService;
    /**
     * 新增用户
     * @param user User
     * @return String
     */
    @PostMapping("/saveUser")
    public String saveUser(@RequestBody User user) {
        int i = userService.saveUser(user);
        return i == 1 ? user.toString() : "新增用户失败";
    }
    /**
     * 获取用户信息
     *
     * @return String
     */
    @GetMapping("/getUser/{id}")
    public String getUser(@PathVariable(value = "id") String id) {
        User user = userService.getUser(id);
        return user.toString();
    }
}
package com.uncle.junittest;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.uncle.junittest.mapper")
public class JunitTestApplication {
    public static void main(String[] args) {
        SpringApplication.run(JunitTestApplication.class, args);
    }
}
spring:
  datasource:
    username: root
    password: 
    url: jdbc:mysql://@rm-2ze44u6e2z7t924t0xo.mysql.rds.aliyuncs.com:3306/
server:
  port: 80
mybatis:
  configuration:
    map-underscore-to-camel-case: true
package com.uncle.junittest.controller;
import com.alibaba.fastjson.JSON;
import com.uncle.junittest.bean.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
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.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
/**
 * 用户测试类
 *
 * @AutoConfigureMockMvc:该注解将会自动配置mockMvc的单元测试
 */
@DisplayName(value = "用户测试类")
@Slf4j
@AutoConfigureMockMvc
@SpringBootTest
class UserControllerTest {
    /**
     * 每个单元测试之前都要执行
     */
    @BeforeEach
    void testBeforeEach() {
        log.info("测试要开始了!");
    }
    /**
     * 每个单元测试之后都要执行
     */
    @AfterEach
    void testAfterEach() {
        log.info("测试结束了!");
    }
    /**
     * 在所有测试之前执行
     */
    @BeforeAll
    static void testBeforeAll() {
        log.info("要测试所有测试了!");
    }
    /**
     * 在所有测试之后执行
     */
    @AfterAll
    static void testAfterAll() {
        log.info("所有测试测试完了!");
    }
    /**
     * mockmvc
     */
    @Autowired
    MockMvc mockMvc;
    /**
     * 新增用户接口测试
     *
     * @Disabled:该注解标识着不需要启动测试
     * @Transactional:事物回滚
     */
    @Disabled
    @DisplayName("新增用户接口测试")
    @Transactional
    @Test
    void saveUser() throws Exception {
        // 请求地址
        String urlTemplate = "/test/saveUser";
        /* 请求头集合 */
        HttpHeaders headers = new HttpHeaders();
        headers.add("header1", "header1");
        headers.add("header2", "header2");
        /* 参数体 */
        String uuid = UUID.randomUUID().toString();
        User user = new User();
        user.setId("4");
        user.setFullName("copico");
        user.setUserName("kebike");
        user.setPassword("123456");
        user.setAge(19);
        user.setSex("女");
        // 转换成json对象
        String jsonString = JSON.toJSONString(user);
        // 请求
        RequestBuilder request = MockMvcRequestBuilders
                // post请求
                .post(urlTemplate)
                // 数据类型
                .contentType(MediaType.APPLICATION_JSON)
                // 请求头
                .headers(headers)
                // 请求体
                .content(jsonString);
        MvcResult mvcResult = mockMvc.perform(request)
                // 打印日志
                .andDo(print())
                // 获取返回值
                .andReturn();
        // 从返回值获取状态码
        int status = mvcResult.getResponse().getStatus();
        //从返回值获取响应的内容
        String contentAsString = mvcResult.getResponse().getContentAsString();
        // 断言
        assertAll("heading",
                () -> assertEquals(200, status, "保存失败"),
                () -> assertEquals(user.toString(), contentAsString, "数据保存失败"));
    }
    /**
     * 获取用户接口测试
     */
    @DisplayName("获取用户接口测试")
    @Test
    void getUser() throws Exception {
        // 预期结果
        User user = new User("1",
                "小软",
                "neusoft",
                "123456",null,null);
        // 请求地址
        String urlTemplate = "/test/getUser/1";
        /* 请求头集合 */
        HttpHeaders headers = new HttpHeaders();
        headers.add("headers1", "headers1");
        headers.add("headers2", "headers2");
        LinkedMultiValueMap<String,String> linkedMultiValueMap = new LinkedMultiValueMap();
        linkedMultiValueMap.add("params1", "params1");
        linkedMultiValueMap.add("params2", "params2");
        // 请求
        RequestBuilder request = MockMvcRequestBuilders
                // post请求
                .get(urlTemplate)
                // 数据类型
                .contentType(MediaType.APPLICATION_JSON)
                // 请求头
                .header("header1","header1")
                .headers(headers)
                // 请求参数
                .param("param1","param2")
                .params(linkedMultiValueMap);
        MvcResult mvcResult = mockMvc.perform(request)
                // 打印日志
                .andDo(print())
                // 获取返回值
                .andReturn();
        // 从返回值获取状态码
        int status = mvcResult.getResponse().getStatus();
        //从返回值获取响应的内容
        String contentAsString = mvcResult.getResponse().getContentAsString();
        // 断言
        assertAll("heading",
                () -> assertEquals(200, status, "用户信息获取失败"),
                () -> assertEquals(user.toString(), contentAsString, "数据不一致,获取失败"));
    }
    /**
     *
     * 规定方法的超时时间
     * 超出时间测试异常
     * @throws InterruptedException
     */
    @DisplayName(value = "超出时间测试")
    @Timeout(value = 500,unit = TimeUnit.MILLISECONDS)
    @Test
    void testTimeout() throws InterruptedException {
        Thread.sleep(1000);
    }
    /**
     * 重复测试n次
     */
    @RepeatedTest(value = 2)
    void repeatedTest() {
        assertArrayEquals(new int[]{1, 2}, new int[]{1, 2});
    }
}
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.uncle</groupId>
    <artifactId>junit-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>junit-test</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
<!--            <version>5.1.47</version>-->
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>



相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
人工智能 自然语言处理 开发者
Copilot的基本原理
【2月更文挑战第13天】Copilot的基本原理
787 3
Copilot的基本原理
|
SQL JSON 分布式计算
Spark SQL快速入门(基础)
Spark SQL快速入门(基础)
673 0
Spark SQL快速入门(基础)
RMAN备份及恢复归档日志的语法
RMAN备份及恢复归档日志的语法
1396 0
|
10月前
|
存储 机器学习/深度学习 人工智能
2025年阿里云GPU服务器租用价格、选型策略与应用场景详解
随着AI与高性能计算需求的增长,阿里云提供了多种GPU实例,如NVIDIA V100、A10、T4等,适配不同场景。2025年重点实例中,V100实例GN6v单月3830元起,适合大规模训练;A10实例GN7i单月3213.99元起,适用于混合负载。计费模式有按量付费和包年包月,后者成本更低。针对AI训练、图形渲染及轻量级推理等场景,推荐不同配置以优化成本和性能。阿里云还提供抢占式实例、ESSD云盘等资源优化策略,支持eRDMA网络加速和倚天ARM架构,助力企业在2025年实现智能计算的效率与成本最优平衡。 (该简介为原文内容的高度概括,符合要求的字符限制。)
|
9月前
|
弹性计算 运维 安全
阿里云服务器通用算力型u1实例简单测评:性能、优势与最新价格参考
在阿里云2025年的活动中,独享型通用算力u1云服务器是用户比较关注的云服务器,因为它的性能要比活动内的经济型e实例好,但是价格又比计算型c8i、通用型g8i等其他企业级实例的价格要便宜。那么,独享型通用算力u1云服务器到底怎么样呢?它又有哪些优势呢?接下来,本文将为您详细解析。
|
Prometheus 监控 Cloud Native
docker安装prometheus+Granfan并监控容器
【9月更文挑战第14天】本文介绍了在Docker中安装Prometheus与Grafana并监控容器的步骤,包括创建配置文件、运行Prometheus与Grafana容器,以及在Grafana中配置数据源和创建监控仪表盘,展示了如何通过Prometheus抓取数据并利用Grafana展示容器的CPU使用率等关键指标。
726 1
|
存储 Linux 数据中心
docker的底层原理四: 资源隔离
本文详细解释了Docker利用Linux内核的Namespace和Cgroups技术实现资源隔离,包括CPU、内存、网络、存储、文件系统、进程间通信、用户和用户组以及进程ID和主机名的隔离,确保容器的独立性和系统的安全性。
707 0
|
调度 索引
NR PUCCH(一) PUCCH format 0/1
NR中PUCCH物理信道用来发送上行控制信息Uplink Control Information(UCI),当然UCI也可以在PUSCH上发送。UCI 内容包括:CSI,HARQ ACK/NACK ,SR 及上述三者的组合信息。
|
存储 NoSQL 大数据
大数据存储:HBase与Cassandra的对比
【7月更文挑战第16天】HBase和Cassandra作为两种流行的分布式NoSQL数据库,在数据模型、一致性模型、数据分布、查询语言和性能等方面各有千秋。HBase适用于需要强一致性和与Hadoop生态系统集成的场景,如大规模数据处理和分析。而Cassandra则更适合需要高可用性和灵活查询能力的场景,如分布式计算、云计算和大数据应用等。在实际应用中,选择哪种数据库取决于具体的需求和场景。希望本文的对比分析能够帮助读者更好地理解这两种数据库,并做出明智的选择。
1102 1
|
消息中间件
分布式篇问题之通过本地消息表实现分布式事务的最终一致性问题如何解决
分布式篇问题之通过本地消息表实现分布式事务的最终一致性问题如何解决
517 0