Spring Boot RestApi 测试教程 Mock 的使用

测试 Spring Boot Web 的时候,我们需要用到 MockMvc,即系统伪造一个 mvc 环境。本章主要编写一个基于 RESTful API 正删改查操作的测试用例。本章最终测试用例运行结果如下:

1 MockMvc 简介

Spring Boot Web 项目中我们采用 MockMvc 进行模拟测试

方法 说明
mockMvc.perform 执行一个请求
MockMvcRequestBuilders.get("XXX") 构造一个请求
ResultActions.param 添加请求传值
ResultActions.accept()) 执行一个请求 如MediaType.TEXT_HTML_VALUE
ResultActions.andExpect 添加执行完成后的断言。 等同于 Assert.assertEquals
ResultActions.andDo 添加一个结果处理器,表示要对结果做点什么事情,比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息
ResultActions.andReturn 表示执行完成后返回相应的结果。

示例,注意注释部分与 addExpect 是等效的,就是断言

 /**
     * 测试 Hello World 方法
     * */
    @Test
    public void hello() throws  Exception{
       MvcResult mvcResult= mockMvc.perform(MockMvcRequestBuilders.get("/api/user/hello")
                .param("name","fishpro")
                .accept(MediaType.TEXT_HTML_VALUE)) //perform 结束
                .andExpect(MockMvcResultMatchers.status().isOk()) //添加断言
                .andExpect(MockMvcResultMatchers.content().string("Hello fishpro"))//添加断言
                .andDo(MockMvcResultHandlers.print()) //添加执行
                .andReturn();//添加返回

        //下面部分等等与 addExcept 部分
        //        int status=mvcResult.getResponse().getStatus();                 //得到返回代码
        //        String content=mvcResult.getResponse().getContentAsString();    //得到返回结果
        //        Assert.assertEquals(200,status);                        //等于 andExpect(MockMvcResultMatchers.status().isOk()) //添加断言
        //        Assert.assertEquals("Hello World",content);            //andExpect(MockMvcResultMatchers.content().string("Hello World"))//添加断言
    }

围绕 MockMvc 其实就是几个核心的问题

  1. 如何初始化对应类的mockMvc
  2. 如何建立请求包括请求的 url、请求的c ontentType
  3. 如何发送请求参数,包括如何发送 url 参数、form 参数、 json 参数、xml 参数
  4. 如何编写断言判断
  5. 如何打印信息
  6. MockMvc本身的返回

2 代码实例

本项目主要使用 SpringBoot RESTful API 架构风格实践 代码。你可以下载此代码,也可以重新新建一个 Spring Boot 项目用于测试。

2.1 创建一个 Spring Boot 项目

2.2 pom.xml 依赖管理

除了 web 引入,其他不需要增加额外的依赖

2.2 编写 Restful 接口部分

新建包 controller 在 com.fishpro.resttest.controller 下新建 UserController.java ,本示例中安装 Restful API 标准编写了 增删改查接口。

package com.fishpro.resttest.controller;

import com.fishpro.resttest.domain.UserDO;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;

/**
 * RESTful API 风格示例 对资源 user 进行操作
 * 本示例没有使用数据库,也没有使用 service 类来辅助完成,所有操作在本类中完成
 * 请注意几天
 *    1.RESTful 风格使用 HttpStatus 状态返回 GET PUT PATCH DELETE 通常返回 201 Create ,DELETE 还有时候返回 204 No Content
 *    2.使用 RESTful 一定是要求具有幂等性,GET PUT PATCH DELETE 本身具有幂等性,但 POST 不具备,无论规则如何定义幂等性,需要根据业务来设计幂等性
 *    3.RESTful 不是神丹妙药,实际应根据实际情况来设计接口
 * */
@RestController
@RequestMapping("/api/user")
public class UserController {
    /**
     * 模拟一组数据
     * */
    private List<UserDO> getData(){
        List<UserDO> list=new ArrayList<>();

        UserDO userDO=new UserDO();
        userDO.setUserId(1);
        userDO.setUserName("admin");
        list.add(userDO);

        userDO=new UserDO();
        userDO.setUserId(2);
        userDO.setUserName("heike");
        list.add(userDO);

        userDO=new UserDO();
        userDO.setUserId(3);
        userDO.setUserName("tom");
        list.add(userDO);

        userDO=new UserDO();
        userDO.setUserId(4);
        userDO.setUserName("mac");
        list.add(userDO);

        return  list;
    }

    /**
     * 测试用 参数为 name
     * */
    @RequestMapping("/hello")
    public String hello(String name){
        return "Hello "+name;
    }

    /**
     * SELECT 查询操作,返回一个JSON数组
     * 具有幂等性
     * */
    @GetMapping("/users")
    @ResponseStatus(HttpStatus.OK)
    public Object getUsers(){
        List<UserDO> list=new ArrayList<>();

        list=getData();

        return list;
    }

    /**
     * SELECT 查询操作,返回一个新建的JSON对象
     * 具有幂等性
     * */
    @GetMapping("/users/{id}")
    @ResponseStatus(HttpStatus.OK)
    public Object getUser(@PathVariable("id") String id){

        if(null==id){
            return  null;
        }

        List<UserDO> list= getData();
        UserDO userDO=null;
        for (UserDO user:list
                ) {
            if(id.equals(user.getUserId().toString())){
                userDO=user;
                break;
            }
        }

        return userDO;
    }

    /**
     * 新增一个用户对象
     * 非幂等
     * 返回 201 HttpStatus.CREATED 对创建新资源的 POST 操作进行响应。应该带着指向新资源地址的 Location 头
     * */
    @PostMapping("/users")
    @ResponseStatus(HttpStatus.CREATED)
    public Object addUser(@RequestBody UserDO user){

        List<UserDO> list= getData();
        list.add(user);//模拟向列表中增加数据
        return user;
    }

    /**
     * 编辑一个用户对象
     * 幂等性
     * */
    @PutMapping("/users/{id}")
    @ResponseStatus(HttpStatus.CREATED)
    public Object editUser(@PathVariable("id") String id,@RequestBody UserDO user){
        List<UserDO> list= getData();
        for (UserDO userDO1:list
                ) {
            if(id.equals(userDO1.getUserId().toString())){
                userDO1=user;
                break;
            }
        }

        return user;
    }

    /**
     * 删除一个用户对象
     * 幂等性
     * 返回 HttpStatus.NO_CONTENT 表示无返回内容
     * */
    @DeleteMapping("/users/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteUser(@PathVariable("id") String id){
        List<UserDO> list= getData();
        UserDO userDO=null;
        for (UserDO user:list
                ) {
            if(id.equals(user.getUserId().toString())){
                //删除用户
                userDO=user;
                break;
            }
        }
    }
}

2.3 编写测试部分

  1. 新建类 com.fishpro.resttest.UserControllerTests
  2. 给测试类增加测试注解 @RunWith(SpringRunner.class) @SpringBootTest,增加mockMvc私有变量
  3. 在 @Before方法体初始化mocMvc
    ```java
    /**

    • 初始化 MockMvc
    • */
      @Before
      public void setUp(){

      mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
      }
      ```

  4. 编写测试方法,在方法名上增加 @Test注解,在方法体内 使用 mockmvc 进行测试
    • mockMvc.perform 执行一个请求
    • MockMvcRequestBuilders.get("") 构造一个请求
    • ResultActions.param 添加请求传值,注意这里的param只能传递 url中的值,@RequestBody 是需要 contentType().content(json二进制)传递的

UserControllerTests.java 代码

/**
 * 本示例针对 Restful API 风格接口做全面的测试用例
 * fishpro at 2019-07-20
 * 注意事项
 *    1.param(name,value) 只能用于 url 参数传递,form 表单传递
 *    2. @RequestBody 方法,对应使用 .contentType(MediaType.APPLICATION_JSON).content(json 字符串)
 *    3.大部分测试用例失败的原因是传递参数对应的contentType不正确
 * */
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTests {

    private MockMvc mockMvc;//定义一个 MockMvc

    /**
     * 初始化 MockMvc 通过MockMvcBuilders.standaloneSetup 模拟一个 UserController 测试环境,通过build得到一个MockMvc
     * */
    @Before
    public void setUp(){

        mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
    }
    /**
     * 测试 Hello World 方法
     * hello 方法是一个 get 方法,使用了 url 参数传递参数 所以使用了 .param 来传递参数
     * accept(MediaType.TEXT_HTML_VALUE) 来设置传递值接收类型
     * */
    @Test
    public void hello() throws  Exception{
       MvcResult mvcResult= mockMvc.perform(MockMvcRequestBuilders.get("/api/user/hello")
                .param("name","fishpro")
                .accept(MediaType.TEXT_HTML_VALUE)) //perform 结束
                .andExpect(MockMvcResultMatchers.status().isOk()) //添加断言
                .andExpect(MockMvcResultMatchers.content().string("Hello fishpro"))//添加断言
                .andDo(MockMvcResultHandlers.print()) //添加执行
                .andReturn();//添加返回

        //下面部分等等与 addExcept 部分
        //int status=mvcResult.getResponse().getStatus();                 //得到返回代码
        //String content=mvcResult.getResponse().getContentAsString();    //得到返回结果
        //Assert.assertEquals(200,status);                        //等于 andExpect(MockMvcResultMatchers.status().isOk()) //添加断言
        //Assert.assertEquals("Hello World",content);            //andExpect(MockMvcResultMatchers.content().string("Hello World"))//添加断言
    }

    /**
     * 测试用户列表获取 /users GET
     * */
    @Test
    public void getUsers() throws  Exception{
        mockMvc.perform(MockMvcRequestBuilders.get("/api/user/users")
                .accept(MediaType.APPLICATION_JSON)) //perform 结束
                .andExpect(MockMvcResultMatchers.status().isOk()) //andExpect
                .andDo(MockMvcResultHandlers.print()) //andDo
                .andReturn();//andReturn
    }

    /**
     * 获取单个用户信息 /users/3 GET
     * */
    @Test
    public void getUser() throws  Exception{
        mockMvc.perform(MockMvcRequestBuilders.get("/api/user/users/3")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }

    /**
     * 新增单个用户信息 /users/ POST
     * 注意 addUser 使用了 @RequestBody 方法,对应使用 .contentType(MediaType.APPLICATION_JSON).content(json 字符串)
     * */
    @Test
    public void addUser() throws  Exception{
        mockMvc.perform(MockMvcRequestBuilders.post("/api/user/users")
                .contentType(MediaType.APPLICATION_JSON).content("{ \"userId\": 3,\"userName\": \"tom\"}"))
                .andExpect(MockMvcResultMatchers.status().isCreated())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }

    /**
     * 编辑一个用户 /users/ PUT
     * */
    @Test
    public void editUser() throws  Exception{
        mockMvc.perform(MockMvcRequestBuilders.put("/api/user/users/3")
                .contentType(MediaType.APPLICATION_JSON).content("{ \"userId\": 3,\"userName\": \"tom\"}"))
                .andExpect(MockMvcResultMatchers.status().isCreated())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }

    /**
     * 删除一个用户 /users/ DELETE
     * */
    @Test
    public void deleteUser() throws  Exception{
        mockMvc.perform(MockMvcRequestBuilders.delete("/api/user/users/3")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isNoContent())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }
}

2.4 运行实例

右键测试类 选择 Run UserControllerTests with Coverage

2.4 值得注意的几个问题

1.请求结果为400 406 (httpstatus)
这个原因通常是 请求参数设置不正确,如 json 应该使用

.contentType(MediaType.APPLICATION_JSON).content("{ \"userId\": 3,\"userName\": \"tom\"}"))

原文地址:https://www.cnblogs.com/fishpro/p/spring-boot-study-resttest-mock.html

时间: 2024-08-29 14:05:56

Spring Boot RestApi 测试教程 Mock 的使用的相关文章

Spring Boot JPA 使用教程

JPA 是 Spring Boot 官方推荐的数据库访问组件,其充分体现了面向对象编程思想,有点像 asp.net 的 EFCore.JPA 也是众多 ORM 的抽象. 从本系列开始,都需要用到 mysql 数据库 和其他一些参考的数据库.请准备相关环节.本章需要以下环境支撑: mysql 5.6+ jdk1.8+ spring boot 2.1.6 idea 2018.1 本项目源码下载 1 数据准备 数据库教程系列都是使用相同的数据,如在 Spring Boot JDBC 使用教程使用的一样

Spring Boot 2.x教程-Thymeleaf 原理是什么

layout: post title: Spring Boot 2.x教程-Thymeleaf 原理是什么 categories: SpringBoot description: Spring Boot 2.x教程-Thymeleaf 原理是什么 keywords: SpringBoot, Spring, Thymeleaf --- 如要要理清楚 Thymeleaf 的原理,那么就要从模板引擎的原理说起.Thymeleaf只不过是众多模板中的一员,功能是一致的. 例如 JSP 也是一种模板. 1

Spring Boot(十二):Spring Boot 如何测试打包部署

有很多网友会时不时的问我, Spring Boot 项目如何测试,如何部署,在生产中有什么好的部署方案吗?这篇文章就来介绍一下 Spring Boot 如何开发.调试.打包到最后的投产上线. 开发阶段 单元测试 在开发阶段的时候最重要的是单元测试了, Spring Boot 对单元测试的支持已经很完善了. 1.在 pom 包中添加 spring-boot-starter-test 包引用 <dependency> <groupId>org.springframework.boot&

spring boot RESTfuldemo测试类

RESTful 架构一个核心概念是“资源”(Resource).从 RESTful 的角度看,网络里的任何东西都是资源,它可以是一段文本.一张图片.一首歌曲.一种服务等,每个资源都对应一个特定的 URI(统一资源定位符),并用它进行标示,访问这个 URI 就可以获得这个资源. 互联网中,客户端和服务端之间的互动传递的就只是资源的表述,我们上网的过程,就是调用资源的 URI,获取它不同表现形式的过程.这种互动只能使用无状态协议 HTTP,也就是说,服务端必须保存所有的状态,客户端可以使用 HTTP

Spring Boot Shiro 使用教程

Apache Shiro 已经大名鼎鼎,搞 Java 的没有不知道的,这类似于 .Net 中的身份验证 form 认证.跟 .net core 中的认证授权策略基本是一样的.当然都不知道也没有关系,因为所有的权限都是模拟的人或机构的社会行为. 本系列从简单的权限讲起,主要涉及到 Shiro.Spring Security.Jwt.OAuth2.0及其他自定义权限策略. 本章主要讲解 Shiro 的基本原理与如何使用,本章主要用到以下基础设施: jdk1.8+ spring boot 2.1.6

Spring Boot应用的测试——Mockito

Spring Boot应用的测试——Mockito Spring Boot可以和大部分流行的测试框架协同工作:通过Spring JUnit创建单元测试:生成测试数据初始化数据库用于测试:Spring Boot可以跟BDD(Behavier Driven Development)工具.Cucumber和Spock协同工作,对应用程序进行测试. 进行软件开发的时候,我们会写很多代码,不过,再过六个月(甚至一年以上)你知道自己的代码怎么运作么?通过测试(单元测试.集成测试.接口测试)可以保证系统的可维

Spring Boot 2.0 图文教程 | 集成邮件发送功能

文章首发自个人微信公众号: 小哈学Java 个人网站: https://www.exception.site/springboot/spring-boots-send-mail 大家好,后续会间断地奉上一些 Spring Boot 2.x 相关的博文,包括 Spring Boot 2.x 教程和 Spring Boot 2.x 新特性教程相关,如 WebFlux 等.还有自定义 Starter 组件的进阶教程,比如:如何封装一个自定义图床 Starter 启动器(支持上传到服务器内部,阿里 OS

Spring Boot开发之流水无情(二)

上篇散仙写了一个很简单的入门级的Spring Boot的例子,没啥技术含量,不过,其实学任何东西只要找到第一个突破口,接下来的事情就好办了,人最怕什么? 我想莫过于干一件事情,没有下手的地方了,而当你一旦找到了这感觉,就可以很喜悦的顺藤摸瓜般的探索你强烈想探索求知的某种事物了,这种冥冥之中玄而又玄的感觉是什么?回想一下: (1) 当你把第一个某种编程语言的Hello World的例子,成功的运行在一个IDE中  (2) 当你第一次从老家出发到达了某个你从未涉足过的地方  (3) 当你成功的完成了

为什么说 Java 程序员到了必须掌握 Spring Boot 的时候?

Spring Boot 2.0 的推出又激起了一阵学习 Spring Boot 热,就单从我个人的博客的访问量大幅增加就可以感受到大家对学习 Spring Boot 的热情,那么在这么多人热衷于学习 Spring Boot 之时,我自己也在思考: Spring Boot 诞生的背景是什么?Spring 企业又是基于什么样的考虑创建 Spring Boot? 传统企业使用 Spring Boot 会给我们带来什么样变革? 带着这些问题,我们一起来了解下 Spring Boot 到底是什么? Spr