测试 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
    /**
    * 初始化 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\"}"))

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

  1. Spring Boot JPA 使用教程

    JPA 是 Spring Boot 官方推荐的数据库访问组件,其充分体现了面向对象编程思想,有点像 asp.net 的 EFCore.JPA 也是众多 ORM 的抽象. 从本系列开始,都需要用到 my ...

  2. spring boot应用测试框架介绍

    一.spring boot应用测试存在的问题 官方提供的测试框架spring-boot-test-starter,虽然提供了很多功能(junit.spring test.assertj.hamcres ...

  3. Spring Boot Mybatis 使用教程

    Mybatis 在当下互联网开发环境,十分重要.本章主要讲述 Mybatis 如何使用. 从本系列开始,都需要用到 mysql 数据库 和其他一些参考的数据库.请准备相关环节.本章需要以下环境支撑: ...

  4. Spring Boot(十二):spring boot如何测试打包部署

    Spring Boot(十二):spring boot如何测试打包部署 一.开发阶段 1,单元测试 在开发阶段的时候最重要的是单元测试了,springboot对单元测试的支持已经很完善了. (1)在p ...

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

    layout: post title: Spring Boot 2.x教程-Thymeleaf 原理是什么 categories: SpringBoot description: Spring Boo ...

  6. Spring Boot从入门到放弃-Spring Boot 整合测试

    站长资讯摘要:使用Spring Boot 整合测试,对Controller 中某个方法进行测试或者对Service,Mapper等进行测试,不需要运行项目即可查看运行结果是否和期望值相同,Spring ...

  7. Spring Boot 2.0 教程 | AOP 切面统一打印请求日志

    欢迎关注微信公众号: 小哈学Java 文章首发于个人网站 https://www.exception.site/springboot/spring-boot-aop-web-request 本节中,您 ...

  8. Spring Boot 2 快速教程:WebFlux 集成 Mongodb(四)

    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第104篇原创 文章工程:* JDK 1.8* M ...

  9. Spring Boot 2 快速教程:WebFlux 快速入门(二)

    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 02:WebFlux 快速入门实践 文章工程: JDK 1.8 ...

随机推荐

  1. APP 安全测试

    http://www.cnblogs.com/wetest/p/6694529.html

  2. 【原】AMFObject数据格式详解

    AMF AMF是Action Message Format(动作消息格式)的简写,它是一种二进制的数据格式.它的设计是为了把actionscript里面的数据(包括Object, Array, Boo ...

  3. Attribute "resultType" must be declared for element type "update" or "insert"

    仔细查看错误如图所示: 解决错误就是把resultType去掉,因为在insert和update语句中是没有返回值的.小坑小坑 转自:https://blog.csdn.net/u013144287/ ...

  4. arcgis中的Join(合并连接)和Relate(关联连接)

    arcgis中的Join(合并连接)和Relate(关联连接) 一.区别 1.连接关系不一样. Relate(关联连接)方式连接的两个表之间的记录可以是“一对一”.“多对一”.“一对多”的关系 Joi ...

  5. win7安装mysql数据库

    1. 软件准备,以64位系统为例如果是32位的下载32位压缩包即可] https://dev.mysql.com/downloads/mysql/ 2.下载解压到本地,将解压路径的bin目录配置到环境 ...

  6. Codeforces Round #598 (Div. 3) F. Equalizing Two Strings

    You are given two strings ss and tt both of length nn and both consisting of lowercase Latin letters ...

  7. 断点调试,issubclass和ininstance的使用

    一等公民 只要可以把一个东西赋值给一个变量,这个东西就叫一等公民 断点调试 在想要加断点的地方用鼠标点击一下,你会看到一个红色圆圈 变红的地方,程序执行到,就会暂停 断电应该加载报错之前 绿色箭头表示 ...

  8. 【原】接口mock作用

    1.前后端 接口定义完成 并发开工 2.测试拿到mock接口 编写用例 3.mock接口 模拟异常服务器返回值 500 404 4.mock接口 模拟数据 不修改线上数据库

  9. iptables详解(2):四表五链

    关于iptables中“四表五链”,我们今天来好好唠唠: 1.表的概念: 我们把具有相同功能的规则的集合叫做"表",所以说,不同功能的规则,我们可以放置在不同的表中进行管理,而ip ...

  10. 【C语言】用函数实现两个数排序(指针作函数参数)

    原理就不讲了,这里用来理解指针的使用方法 #include <stdio.h> void fun(int* a,int* b) { int t; if(*a>=*b) { t = * ...