对Controller进行单元测试是Spring框架原生就支持的能力,它可以模拟HTTP客户端发起对服务地址的请求,可以不用借助于诸如Postman这样的外部工具就能完成对接口的测试。

具体来讲,是由Spring框架中的spring-test模块提供的实现,详见MockMvc

如下将详细阐述如何使用MockMvc测试框架实现对“Spring Controller”进行单元测试,基于Spring Boot开发框架进行验证。

添加测试框架依赖:

<!-- Spring框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
!<-- Spring测试框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 文件操作工具 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>

导入静态工具方法

为了便于在编写测试用例时直接调用测试框架自带的静态方法,首先需要导入这些静态工具方法。

需要导入的静态方法如下:

import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.setup.SharedHttpSessionConfigurer.*;

初始化MockMvc

初始化MockMvc有2种方式:

方式1:明确指定需要测试的“Controller”类进行配置

方式2:基于Spring容器进行配置,包含了Spring MVC环境和所有“Controller”类,通常使用这种方式。

@SpringBootTest
public class TestControllerTest { MockMvc mockMvc; // 初始化MockMvc
@BeforeEach
void setUp(WebApplicationContext wac) {
// 方式1:明确指定需要测试的“Controller”类
this.mockMvc = MockMvcBuilders.standaloneSetup(new TestController()).build(); // 方式2:基于Spring容器进行配置,包含了Spring MVC环境和所有“Controller”类。
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
}

另外,还可以对MockMvc进行全局配置。

// 全局配置MockMvc
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.defaultRequest(get("/").accept(MediaType.APPLICATION_JSON)) // 默认请求路径
.apply(sharedHttpSession()) // 配置session
.alwaysExpect(status().isOk()) // 预期响应状态码
.alwaysExpect(content().contentType("application/json;charset=UTF-8")) // 预期内容类型
.build();

执行测试

MockMvc支持对常见的HTTP方法,如:GET,POST,PUT,DELETE等,甚至还支持文件上传请求。

测试GET接口

// 访问GET接口:不带参数
@Test
public void testSimpleGet() throws Exception {
MvcResult result = this.mockMvc.perform(get("/test/simple/get")
.accept(MediaType.APPLICATION_JSON)) // 接受JSON格式响应消息
.andReturn(); // 获取返回结果
Assertions.assertEquals("OK", result.getResponse().getContentAsString());
} // 访问GET接口:带URL参数
@Test
public void testParamGet() throws Exception {
int id = 10;
// 方式1:在URI模板中指定参数
//MvcResult result = this.mockMvc.perform(get("/test/param/get?id={id}", id).accept(MediaType.APPLICATION_JSON)).andReturn(); // 方式2:通过param()方法指定参数
//MvcResult result = this.mockMvc.perform(get("/test/param/get").param("id", String.valueOf(id)).accept(MediaType.APPLICATION_JSON)).andReturn(); // 方式3:通过queryParam()方法指定参数
MvcResult result = this.mockMvc.perform(get("/test/param/get").queryParam("id", String.valueOf(id)).accept(MediaType.APPLICATION_JSON)).andReturn();
Assertions.assertEquals("OK: " + id, result.getResponse().getContentAsString());
}

测试POST接口

// 传递表单参数
@Test
public void testSimplePost() throws Exception {
int id = 10; // 调用param()方法传递参数
MvcResult result = this.mockMvc.perform(post("/test/simple/post")
.param("id", String.valueOf(id))
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.accept(MediaType.APPLICATION_JSON))
.andReturn();
Assertions.assertEquals("{\"id\":10}", result.getResponse().getContentAsString());
} // 传递JSON参数
@Test
public void testSimplePostJson() throws Exception {
// 调用content()方法传递json字符串参数
Subject subject = new Subject();
subject.setId(10);
String content = JSON.toJSONString(subject);
MvcResult result = this.mockMvc.perform(post("/test/simple/post/json")
.content(content)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andReturn();
Assertions.assertEquals("{\"id\":10}", result.getResponse().getContentAsString());
}

测试文件上传

@Test
public void testFileUploadSingle() throws Exception {
File file = new File("C:\\Users\\xxx\\Downloads\\test.jpg");
String fileName = FilenameUtils.getName(file.getName());
byte[] bytes = FileUtils.readFileToByteArray(file);
MockMultipartFile mockMultipartFile = new MockMultipartFile("file", fileName, MediaType.MULTIPART_FORM_DATA_VALUE, bytes);
this.mockMvc.perform(multipart("/test/upload/single").file(mockMultipartFile))
.andExpect(status().isOk())
.andExpect(content().string("OK"))
.andDo(print());
}

定义预期结果

断言响应结果时,有2种方式:

1.使用JUnit提供的Assert断言工具判断返回结果,这是一种非常普遍和常见的方式

2.在MockMvc框架中可以通过andExpect()方法定义一个或多个预期结果,当其中一个期望结果断言失败时,就不会断言其他期望值了

// 使用Junit断言工具判断返回结果是否符合预期
@Test
public void testAssertResult() throws Exception {
MvcResult result = this.mockMvc.perform(get("/test/simple/get").accept(MediaType.APPLICATION_JSON)).andDo(print()).andReturn();
Assert.assertEquals("OK", result.getResponse().getContentAsString());
} // 在MockMvc框架中定义预期结果
@Test
public void testExpectations() throws Exception {
this.mockMvc.perform(get("/test/simple/get").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) // 预期响应状态码为200
.andExpect(content().string("OK")) // 预期返回值为字符串“OK”
.andDo(print());
}

相比于使用Junit的断言工具判断返回结果,在MockMvc框架中直接定义预期结果进行断言检查更加简洁。

写在最后

使用Spring提供的测试框架MockMvc可以非常方便地实现对HTTP服务接口进行单元测试,不要把基础的功能验证工作都交给测试童鞋,应该通过单元测试来保证代码迭代的稳定性。

【参考】

https://blog.csdn.net/coolcoffee168/article/details/88638042 springboot 单元测试 (controller层) 方法 -- MockMvc

如何对Spring MVC中的Controller进行单元测试的更多相关文章

  1. Spring MVC中的Controller是Serlvet吗?

    1. Controller不是Servlet DispatcherServler是Spring MVC中的唯一Servlet,(这点可通过查看FrameworkServlet的子类确认) Servle ...

  2. spring mvc中的文件上传

    使用commons-fileupload上传文件所需要的架包有:commons-fileupload 和common-io两个架包支持,可以到Apache官网下砸. 在配置文件spring-mvc.x ...

  3. Spring MVC中一般 普通类调用service

    在Spring MVC中,Controller中使用service只需使用注解@Resource就行,但是一般类(即不使用@Controller注解的类)要用到service时,可用如下方法: 1.S ...

  4. Spring MVC中一般类使用service

    在Spring MVC中,Controller中使用service只需使用注解@Resource就行,但是一般类(即不使用@Controller注解的类)要用到service时,可用如下方法: 1.S ...

  5. Spring MVC 中的基于注解的 Controller【转】

    原文地址:http://my.oschina.net/abian/blog/128028 终于来到了基于注解的 Spring MVC 了.之前我们所讲到的 handler,需要根据 url 并通过 H ...

  6. Spring MVC中基于注解的 Controller

         终于来到了基于注解的 Spring MVC 了.之前我们所讲到的 handler,需要根据 url 并通过 HandlerMapping 来映射出相应的 handler 并调用相应的方法以响 ...

  7. Spring MVC中,事务是否可以加在Controller层

    一般而言,事务都是加在Service层的,但是爱钻牛角尖的我时常想:事务加在Controller层可不可以.我一直试图证明事务不止可以加在Service层,还可以加在Controller层,但是没有找 ...

  8. Spring MVC 中的基于注解的 Controller(转载)

           终于来到了基于注解的 Spring MVC 了.之前我们所讲到的 handler,需要根据 url 并通过 HandlerMapping 来映射出相应的 handler 并调用相应的方法 ...

  9. Http请求中Content-Type讲解以及在Spring MVC中的应用

    引言: 在Http请求中,我们每天都在使用Content-type来指定不同格式的请求信息,但是却很少有人去全面了解content-type中允许的值有多少,这里将讲解Content-Type的可用值 ...

随机推荐

  1. [vscode] os.getcwd(),调试和命令行运行的结果不一致

    问题描述: 调试和命令行运行的时候工作目录不一致 这会导致一个问题,我想从上级目录导入模块的话,F5调试就会找不到模块,而命令行则没问题 那么我该如何调试呢? 目录结构: top  └ folder_ ...

  2. github 创建网络仓库 ,使用git工具将本地文件上传/删除 --- 心得

    1.前言 使用  git做项目控制版本工具,当然,使用SVN也可以,但是,git让人感觉更先进一些,与GitHub结合,用起来很方便,服务端由官网控制. 而SVN分客户端和服务端,都是个人控制,因此, ...

  3. Echart可视化学习(五)

    文档的源代码地址,需要的下载就可以了(访问密码:7567) https://url56.ctfile.com/f/34653256-527823386-04154f 常见的数据可视化库: D3.js ...

  4. RabbitMQ除开RPC的五种消模型----原生API

    2.五种消息模型 RabbitMQ提供了6种消息模型,但是第6种其实是RPC,并不是MQ,因此不予学习.那么也就剩下5种. 但是其实3.4.5这三种都属于订阅模型,只不过进行路由的方式不同. 通过一个 ...

  5. STM32 EXTI(外部中断)

    一.EXTI 简介 EXTI(External interrupt/event controller)-外部中断/事件控制器,管理了控制器的 20个中断/事件线.每个中断/事件线都对应有一个边沿检测器 ...

  6. 问题记录——BigDecimal保留两位小数及格式化成百分比

    1.函数总结 BigDecimal.setScale()方法用于格式化小数点 setScale(1)表示保留一位小数,默认用四舍五入方式 setScale(1,BigDecimal.ROUND_DOW ...

  7. 解决windows下因为防火墙无法通过go get 下载gin的问题

    使用: go get -u github.com/gin-gonic/gin 出现以下错误: unrecognized import path "gopkg.in/yaml.v2" ...

  8. LINUX系统机器人

    简介 在2016年,国内的软硬件尚不能有效支撑我们制造智能机器人,我们无法有效在Linux进行语音唤醒,只能使用斯坦福大学狮身人面像语音开源项目来进行英文识别我们对RIMA的呼唤,抗干扰性为0,意味着 ...

  9. 字符串自实现(一)(mystrcpy,mystrcat,mystrcmp)

    char* mystrcpy(char* str_one,const char* str_two) { char* tmp = str_one; while (*str_one++ = *str_tw ...

  10. golang中的25个关键字

    Go 语言中会使用到的 25 个关键字或保留字: 1. 程序声明 import 导入 package 包 2. 程序实体声明和定义 chan 通道 var 变量声明 const 常量声明 func 用 ...