前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringMvc 之MockMvc帮我们解决了什么问题

SpringMvc 之MockMvc帮我们解决了什么问题

作者头像
louiezhou001
发布2020-04-07 15:21:03
1.2K0
发布2020-04-07 15:21:03
举报

概述:

对模块进行集成测试时,希望能够通过输入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项目

首先来看下目录结构:

代码语言:javascript
复制
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来管理和构建项目

代码语言:javascript
复制
pom.xml
代码语言:javascript
复制
<?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。

代码语言:javascript
复制
MyWebInitializer.java
代码语言:javascript
复制
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 应用程序的前端控制器。

代码语言:javascript
复制
@Override
protected Class<?>[] getServletConfigClasses() {

    return new Class[]{WebConfig.class};
}

Getservletconfigclasses ()返回一个 web 配置类。

代码语言:javascript
复制
WebConfig.java
代码语言:javascript
复制
@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 引擎。

代码语言:javascript
复制
MyController.java
代码语言:javascript
复制

@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 ()方法返回纯文本消息。在我们的测试中,我们测试这两种方法。

代码语言:javascript
复制
index.html
代码语言:javascript
复制
<!DOCTYPE html>
<html xmlns:th="http://www.yourself.com">
<head>
    <meta charset="UTF-8">
    <title>Home page</title>
</head>
<body>
<p>
Today is: <span th:text="${now}"></span>
</p>

</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测试

编写测试类

代码语言:javascript
复制
MyControllerTest.java
代码语言:javascript
复制

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. /**
  2. * 1、mockMvc.perform执行一个请求。
  3. * 2、MockMvcRequestBuilders.get("XXX")构造一个请求。
  4. * 3、ResultActions.param添加请求传值
  5. * 4、ResultActions.accept(MediaType.TEXT_HTML_VALUE))设置返回类型
  6. * 5、ResultActions.andExpect添加执行完成后的断言。
  7. * 6、ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情
  8. * 比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。
  9. *7、ResultActions.andReturn表示执行完成后返回相应的结果。
  10. */

测试两个处理程序

代码语言:javascript
复制
private MockMvc mockMvc;

@Before
public void setup() {
    this.mockMvc = MockMvcBuilders.standaloneSetup(new MyController()).build();
}

我们建立了 MockMvc。我们将 MyController 添加到独立设置中。Mockmvcbuilders.standalonesetup ()允许注册一个或多个控制器,而不需要使用完整的 WebApplicationContext。

代码语言:javascript
复制
@Test
public void testHomePage() throws Exception {
    this.mockMvc.perform(get("/"))
            .andExpect(status().isOk())
            .andExpect(view().name("index"))
            .andDo(MockMvcResultHandlers.print());
}

我们测试主页, 验证状态和返回的视图名称, 同时还打印结果。

代码语言:javascript
复制
@Test
public void testMessagePage() throws Exception {
    this.mockMvc.perform(get("/message"))
            .andExpect(status().isOk())
            .andExpect(content().string("Hello SpringMvc!"));
}

我们测试消息页面,因为它是一个 RESTful 方法,所以我们验证状态和返回的字符串。

我们使用 mvn test 测试运行测试, 看下执行结果

代码语言:javascript
复制
-------------------------------------------------------
 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:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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():得到响应内容验证器;
JsonPathResultMatchers jsonPath(String expression, Object ... args)/ResultMatcher jsonPath(String expression, Matcher matcher):得到Json表达式验证器;
XpathResultMatchers xpath(String expression, Object... args)/XpathResultMatchers xpath(String expression, Map<string, string=""> namespaces, Object... args):得到Xpath表达式验证器;
ResultMatcher forwardedUrl(final String expectedUrl):验证处理完请求后转发的url(绝对匹配);
ResultMatcher forwardedUrlPattern(final String urlPattern):验证处理完请求后转发的url(Ant风格模式匹配,@since spring4);
ResultMatcher redirectedUrl(final String expectedUrl):验证处理完请求后重定向的url(绝对匹配);
ResultMatcher redirectedUrlPattern(final String expectedUrl):验证处理完请求后重定向的url(Ant风格模式匹配,@since spring4);

总结:

测试是一门技术, 更是一门艺术. 也许你今天拥有的技术, 明天就会被淘汰. 同时需要我们开拓思维和眼界, 积极拥抱变化, 学习新知识, 新方法,新技能, 计算机领域讲究的是实践, 学习更要讲究方式方法. 学习和动手一定要结合, 光看不练,犹如看武功秘籍, 是永远成不了武功大侠的。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 测试工程师成长之道 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 应用示例: Spring MockMvc Example
  • MockHttpServletRequestBuilder:
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档