SpringMvc 之MockMvc帮我们解决了什么问题
概述:
对模块进行集成测试时,希望能够通过输入URL对Controller进行测试,如果通过启动服务器,建立http client进行测试,这样会使得测试变得很麻烦,比如启动速度慢,测试验证不方便,依赖网络环境等,导致测试无法进行,为了尽可能的对Controller进行快速测试,通过引入MockMVC进行解决。
MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快, 不依赖网络环境, 而且提供了一整套验证框架工具,这样可以使得请求的验证统一而且方便。
Spring 是一个用于创建企业应用程序的流行 Java 应用程序框架。Mockmvc 被定义为服务器端 springmvc 测试的主要入口点。使用 MockMvc 的测试介于单元测试和集成测试之间。
服务器端测试环境
spring 集成测试中对mock 的集成很好
- Junit必须在4.9版本以上
- spring版本必须在3.2以上
- 使用的框架必须是springMvc框架
- Maven 3
- JDK 1.8
应用示例: Spring MockMvc Example
下面的应用程序使用 MockMvc 测试 springmvc 应用程序。我们为模板和 RESTful 控制器方法创建一个测试。
创建一个maven项目
首先来看下目录结构:
src
├───main
│ ├───java
│ │ └───com
│ │ └───code //被测代码
│ │ ├───config
│ │ │ MyWebInitializer.java
│ │ │ WebConfig.java
│ │ └───controller
│ │ MyController.java
│ ├───resources
│ └───webapp
│ └───WEB-INF
│ └───templates
│ index.html
└───test //测试代码
└───java
└───com
└───code
└───controller
MyControllerTest.java
maven来管理和构建项目
pom.xml
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.code</groupId>
<artifactId>mockmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<!-- 字符集编码-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<spring-version>5.1.3.RELEASE</spring-version>
</properties>
<dependencies>
<!--添加依赖包-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!--添加依赖包 servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!--添加依赖包 Junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--添加依赖包 Springframework webmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-version}</version>
</dependency>
<!--添加依赖包 Springframework test-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
</plugins>
</build>
</project>
在 pom.xml 文件中,我们有以下依赖项: logback-classic、 javax.servlet-api、 junit、 spring-webmvc、 spring-test、 thymeleaf-spring5和 thymeleaf。
MyWebInitializer.java
package com.code.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MyWebInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return null;
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
protected String[] getServletMappings() {
return new String[]{"/"};
Mywebinitializer 注册 Spring DispatcherServlet,它是 Spring web 应用程序的前端控制器。
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
Getservletconfigclasses ()返回一个 web 配置类。
WebConfig.java
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.code"})
public class WebConfig {
@Autowired
private ApplicationContext applicationContext;
@Bean
public SpringResourceTemplateResolver templateResolver(){
SpringResourceTemplateResolver templateResolver=new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
return templateResolver;
@Bean
public SpringTemplateEngine templateEngine(){
SpringTemplateEngine templateEngine=new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
@Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
ViewResolverRegistry registry = new ViewResolverRegistry(null, applicationContext);
resolver.setTemplateEngine(templateEngine());
registry.viewResolver(resolver);
return resolver;
}
Web.config 使用@enablewebmvc 启用 Spring MVC 注释,并配置扫描 com.code 包的组件。它设置了 Thymeleaf 引擎。
MyController.java
@Controller
public class MyController {
long currentTime = System.currentTimeMillis();
@GetMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE) //html类型
public String home(Model model) {
model.addAttribute("now", LocalDateTime.now());
return "index";
@GetMapping(value = "/message", produces = MediaType.TEXT_PLAIN_VALUE)//纯文本类型
@ResponseBody
public String message(HttpServletRequest request,
HttpServletResponse response) throws IOException {
response.addHeader("Content-Type", "application/json;charset=UTF-8");
response.addCookie(new Cookie("cookie","12345678"));
response.addDateHeader("now",currentTime);
return "Hello SpringMvc!";
Mycontroller 提供了两个处理程序方法。Home ()方法返回具有单个属性的视图,message ()方法返回纯文本消息。在我们的测试中,我们测试这两种方法。
index.html
<!DOCTYPE html>
<html xmlns:th="http://www.yourself.com">
<meta charset="UTF-8">
<title>Home page</title>
</head>
Today is: <span th:text="${now}"></span>
</body>
</html>
这是 index. html page
业务代码部分之前的测试方式是打包, 部署到web server 容器里, 且需要包含servlet, 启动web server, 如果你的controller 依赖项目组中其他成员的jar包或者其他, 你还要和他在同一环境里进行联调, 无法实现单独测试controller, 现在有了mockMVC就不同了.
在spring开发中,可以使用Spring自带的MockMvc这个类进行Mock测试。
所谓的Mock测试,举一个通俗易懂的例子,像servlet API中的HttpServletRequest对象是Tomcat容器生成的。我们无法手动的new出来,于是就有了所谓的Mock测试
编写测试类
MyControllerTest.java
public class MyControllerTest {
private MockMvc mockMvc;
@Autowired //注入要测试的Controller
MyController myController=new MyController();
@Before //这个方法在每个方法执行之前都会执行一遍
public void setup() {
System.out.println("----这是一个Junit Before----");
//独立安装测试方式
this.mockMvc = MockMvcBuilders.standaloneSetup(myController).build();
@Test
public void testHome() throws Exception {
//创建一个mockMVC 进行测试
this.mockMvc.perform(get("/").accept(MediaType.TEXT_HTML_VALUE))) //url
.andExpect(status().isOk()) //测试状态码
.andExpect(view().name("index"))
.andDo(MockMvcResultHandlers.print()) //打印MvcResult信息
.andReturn();
@Test
public void testMessage() throws Exception {
ResultActions result=mockMvc.perform(MockMvcRequestBuilders.get("/message")) //构造一个请求
.andExpect(status().isOk())
.andDo(MockMvcResultHandlers.print()) //添加一个结果处理器,表示要对结果做点什么事情, print()输出整个响应结果信息。
.andExpect(content().contentType("application/json;charset=UTF-8")) //添加执行完成后的断言
.andExpect(cookie().value("cookie","12345678")) //测试cookie
.andExpect(header().dateValue("now",System.currentTimeMillis()))
.andExpect(content().string("Hello SpringMvc!"));
result.andExpect(header().dateValue("now",System.currentTimeMillis()));
MockMvcBuilder是用来构造MockMvc的构造器,其主要有两个实现:
StandaloneMockMvcBuilder和DefaultMockMvcBuilder
分别对应两种测试方式,即独立安装和集成Web环境测试(此种方式并不会集成真正的web环境,而是通过相应的Mock API进行模拟测试,无须启动服务器)。对于我们来说直接使用静态工厂MockMvcBuilders创建即可。
- /**
- * 1、mockMvc.perform执行一个请求。
- * 2、MockMvcRequestBuilders.get("XXX")构造一个请求。
- * 3、ResultActions.param添加请求传值
- * 4、ResultActions.accept(MediaType.TEXT_HTML_VALUE))设置返回类型
- * 5、ResultActions.andExpect添加执行完成后的断言。
- * 6、ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情
- * 比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。
- *7、ResultActions.andReturn表示执行完成后返回相应的结果。
- */
测试两个处理程序
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new MyController()).build();
}
我们建立了 MockMvc。我们将 MyController 添加到独立设置中。Mockmvcbuilders.standalonesetup ()允许注册一个或多个控制器,而不需要使用完整的 WebApplicationContext。
@Test
public void testHomePage() throws Exception {
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("index"))
.andDo(MockMvcResultHandlers.print());
}
我们测试主页, 验证状态和返回的视图名称, 同时还打印结果。
@Test
public void testMessagePage() throws Exception {
this.mockMvc.perform(get("/message"))
.andExpect(status().isOk())
.andExpect(content().string("Hello SpringMvc!"));
}
我们测试消息页面,因为它是一个 RESTful 方法,所以我们验证状态和返回的字符串。
我们使用 mvn test 测试运行测试, 看下执行结果
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.code.controller.MyControllerTest
----这是一个Junit Before----
MockHttpServletRequest:
HTTP Method = GET
Request URI = /
Parameters = {}
Headers = {Accept=[text/html]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.code.controller.MyController
Method = public java.lang.String com.code.controller.MyController.home(org.springframework.ui.Model)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = index
View = null
Attribute = now
value = 2020-03 T12:58:43.701
errors = []
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Language=[en]}
Content type = null
Body =
Forwarded URL = index
Redirected URL = null
Cookies = []
----这是一个Junit Before----
MockHttpServletRequest:
HTTP Method = GET
Request URI = /message
Parameters = {}
Headers = {}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.code.controller.MyController
Method = public java.lang.String com.code.controller.MyController.message(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Type=[application/json;charset=UTF-8], Set-Cookie=[cookie=12345678], now=[Mon, Mar 2020 04:58:43 GMT], Content-Length=[16]}
Content type = application/json;charset=UTF-8
Body = Hello SpringMvc!
Forwarded URL = null
Redirected URL = null
Cookies = [[Cookie@5939a379 name = 'cookie', value = '12345678', comment = [null], domain = [null], maxAge = -1, path = [null], secure = false, version = 0, httpOnly = false]]
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.131 sec
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.369 s
[INFO] ------------------------------------------------------------------------
整个过程: 1、mockMvc.perform执行一个请求; 2、MockMvcRequestBuilders.get("/")构造一个请求 3、ResultActions.andExpect添加执行完成后的断言 4、ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情,比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。 5、ResultActions.andReturn表示执行完成后返回相应的结果。
perform :执行一个RequestBuilder请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处理;
get :声明发送一个get请求的方法。MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables):根据uri模板和uri变量值得到一个GET请求方式的。另外提供了其他的请求的方法,如:post、put、delete等。
param :添加request的参数,如发送请求的时候带上了了pcode = root的参数。假如使用需要发送json数据格式的时将不能使用这种方式。
andExpect :添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确(对返回的数据进行的判断);
andDo :添加ResultHandler结果处理器,比如调试时打印结果到控制台(对返回的数据进行的判断);
andReturn :最后返回相应的MvcResult;然后进行自定义验证/进行下一步的异步处理(对返回的数据进行的判断)
在上面的测试类中,我们用到了这么一个类MockMvcRequestBuilders用来构建请求的,此类还有以下主要的API:
MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables):根据uri模板和uri变量值得到一个GET请求方式的MockHttpServletRequestBuilder;如get(/user/{id}, 1L);
MockHttpServletRequestBuilder post(String urlTemplate, Object... urlVariables):同get类似,但是是POST方法;
MockHttpServletRequestBuilder put(String urlTemplate, Object... urlVariables):同get类似,但是是PUT方法;
MockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVariables) :同get类似,但是是DELETE方法;
MockHttpServletRequestBuilder options(String urlTemplate, Object... urlVariables):同get类似,但是是OPTIONS方法;
MockHttpServletRequestBuilder request(HttpMethod httpMethod, String urlTemplate, Object... urlVariables):提供自己的Http请求方法及uri模板和uri变量,如上API都是委托给这个API;
MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, Object... urlVariables):提供文件上传方式的请求,得到MockMultipartHttpServletRequestBuilder;
RequestBuilder asyncDispatch(final MvcResult mvcResult):创建一个从启动异步处理的请求的MvcResult进行异步分派的RequestBuilder;
MockHttpServletRequestBuilder:
MockHttpServletRequestBuilder header(String name, Object... values)/MockHttpServletRequestBuilder headers(HttpHeaders httpHeaders):添加头信息;
MockHttpServletRequestBuilder contentType(MediaType mediaType):指定请求的contentType头信息;
MockHttpServletRequestBuilder accept(MediaType... mediaTypes)/MockHttpServletRequestBuilder accept(String... mediaTypes):指定请求的Accept头信息;
MockHttpServletRequestBuilder content(byte[] content)/MockHttpServletRequestBuilder content(String content):指定请求Body体内容;
MockHttpServletRequestBuilder param(String name,String... values):请求传入参数
MockHttpServletRequestBuilder cookie(Cookie... cookies):指定请求的Cookie;
MockHttpServletRequestBuilder locale(Locale locale):指定请求的Locale;
MockHttpServletRequestBuilder characterEncoding(String encoding):指定请求字符编码;
MockHttpServletRequestBuilder requestAttr(String name, Object value) :设置请求属性数据;
MockHttpServletRequestBuilder sessionAttr(String name, Object value)/MockHttpServletRequestBuilder sessionAttrs(Map<string, object=""> sessionAttributes):设置请求session属性数据;
MockHttpServletRequestBuilder flashAttr(String name, Object value)/MockHttpServletRequestBuilder flashAttrs(Map<string, object=""> flashAttributes):指定请求的flash信息,比如重定向后的属性信息;
MockHttpServletRequestBuilder session(MockHttpSession session) :指定请求的Session;
MockHttpServletRequestBuilder principal(Principal principal) :指定请求的Principal;
MockHttpServletRequestBuilder contextPath(String contextPath) :指定请求的上下文路径,必须以“/”开头,且不能以“/”结尾;
MockHttpServletRequestBuilder pathInfo(String pathInfo) :请求的路径信息,必须以“/”开头;
MockHttpServletRequestBuilder secure(boolean secure):请求是否使用安全通道;
MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor):请求的后处理器,用于自定义一些请求处理的扩展点;
MockMultipartHttpServletRequestBuilder:
MockMultipartHttpServletRequestBuilder继承自MockHttpServletRequestBuilder,又提供了如下API:
MockMultipartHttpServletRequestBuilder file(String name, byte[] content)/MockMultipartHttpServletRequestBuilder file(MockMultipartFile file):指定要上传的文件;
ResultActions:
调用MockMvc.perform(RequestBuilder requestBuilder)后将得到ResultActions,通过ResultActions完成如下三件事:
ResultActions andExpect(ResultMatcher matcher) :添加验证断言来判断执行请求后的结果是否是预期的;
ResultActions andDo(ResultHandler handler) :添加结果处理器,用于对验证成功后执行的动作,如输出下请求/结果信息用于调试;
MvcResult andReturn() :返回验证成功后的MvcResult;用于自定义验证/下一步的异步处理;
ResultMatcher/MockMvcResultMatchers:
ResultMatcher用来匹配执行完请求后的结果验证,其就一个match(MvcResult result)断言方法,如果匹配失败将抛出相应的异常;此类案例中并为使用,请自行查看。具体提供以下API:
HandlerResultMatchers handler():请求的Handler验证器,比如验证处理器类型/方法名;此处的Handler其实就是处理请求的控制器;
RequestResultMatchers request():得到RequestResultMatchers验证器;
ModelResultMatchers model():得到模型验证器;
ViewResultMatchers view():得到视图验证器;
FlashAttributeResultMatchers flash():得到Flash属性验证;
StatusResultMatchers status():得到响应状态验证器;
HeaderResultMatchers header():得到响应Header验证器;
CookieResultMatchers cookie():得到响应Cookie验证器;
ContentResultMatchers content():得到响应内容验证器;