mockMvc测试

1.环境准备

本文主要是介绍mock测试方面的知识,用到的环境是 idea + jdk8 + mysql5.5.49 + Junit5 + springBoot 2.6 + mokito

1.1数据库脚本准备

-- 创建订单表
CREATE TABLE `sale_order` (
  `id` bigint NOT null AUTO_INCREMENT COMMENT '客户订单ID',
  `sale_order_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单编号',
  `customer` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '客户',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uni_sale_order_code` (`sale_order_code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='客户订单(销售订单)';

-- 创建订单颜色明细表
CREATE TABLE `sale_order_detail` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '客户订单明细ID',
  `sale_order_id` bigint NOT NULL COMMENT '客户订单ID',
  `color` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `quantity` bigint DEFAULT NULL COMMENT '数量',
   `receiver_price` decimal(22,2) DEFAULT NULL COMMENT '接单价格',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='客户订单明细表(下单信息)';

1.2.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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.fufulong.demo</groupId>
    <artifactId>mock-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mock-demo</name>
    <description>mock-demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.6.5</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>3.12.4</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.2</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                    <include>**/*.yml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>META-INF/**</include>
                    <include>template/*</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

</project>

1.3 配置文件准备

spring:
  application:
    name: mock-demo
  datasource:
    username: root
    password: xxxx
    url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&rewriteBatchedStatements=true
  servlet:
    multipart:
      max-request-size: 100MB
      max-file-size: 10MB

server:
  port: 9000
  servlet:
    context-path: /mock-demo

2. mockMvc 的使用方法

2.1 初始化mockMvc

使用@SpringBootTest注解会加载整个Context。这样我们可以自动获得所有在Context中注入的Bean,以及从application.properties中加载的配置信息。

@SpringBootTest中声明webEnvironment为WebEnvironment.MOCK(默认值就是WebEnvironment.MOCK)后,结合@AutoConfigureMockMvc注解,在测试的时候会得到一个模拟的Web/Servlet环境。

因为没有Web Server,所以就无法使用RestTemplate,也就只能继续使用MockMVC了。这次MockMVC的实例是由@AutoConfigureMockMvc注解来完成的。这归功于SpringBoot的自动化配置。

junit5和junit4环境,初始化mocKMvc的方法不一样,如果是Junit5,初始化 mockMvc的代码如下:

@SpringBootTest(classes = MockDemoApplication.class)
@AutoConfigureMockMvc
class MockDemoApplicationTests {
    @Autowired
    private MockMvc mockMvc;

}


也可以使用另外一种方式,使用MockMvcBuilders 得静态方法初始化mockMvc,这样做还可以设置每次mock测试都要执行的动作,期望等

mockMvc测试

@SpringBootTest(classes = MockDemoApplication.class)
class MockDemoApplicationTests {
    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @BeforeEach
    public void setUp(){
        // 设定mockMvc能mock的controller是整个springBoot项目环境中的controller
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext). build();
    }
    
    }

2.2 测试post类型的接口

接口方法

@RestController
@RequestMapping("saleOrder")
public class SaleOrderController {

    @Autowired
    private SaleOrderService saleOrderService;

    @PostMapping(value = "/save")
    public DataResponse<Long> save(@RequestBody SaveSaleOrderReq req){
        Long id = saleOrderService.save(req);
        return DataResponse.ok(id);
    }

}

测试方法

@Test
public void test1() throws Exception {
    SaleOrder saveOrder = new SaleOrder();
    saveOrder.setSaleOrderCode("0001");
    saveOrder.setCustomer("小明");

    MockHttpServletRequestBuilder requestBuilder =
        //请求路径,不需要带前面 servletContext部分
        MockMvcRequestBuilders.post("/saleOrder/save")
        // post请求返回的mediaType
        .accept(MediaType.APPLICATION_JSON)
        // post请求的请求内容的 mediaType
        .contentType(MediaType.APPLICATION_JSON)
        // post 请求参数内容
        .content(JSON.toJSONString(saveOrder));
    MvcResult mvcResult = mockMvc.perform(requestBuilder)
        // 设置期望的结果,用 ResultMatcher 来表示
        .andExpect(MockMvcResultMatchers.status().isOk())
        // 内置的打印mock请求结果
        .andDo(MockMvcResultHandlers.print())
        // 正式执行接口,并返回接口的返回值
        .andReturn();
    // 把 mvcResult 的 response对象,转换成 json,然后打印显示
    String s = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
    System.out.println(s);
}

结果如下所示:

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /saleOrder/save
       Parameters = {}
          Headers = [Content-Type:"application/json", Accept:"application/json", Content-Length:"44"]
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = com.fufulong.demo.mockdemo.controller.SaleOrderController
           Method = com.fufulong.demo.mockdemo.controller.SaleOrderController#save(SaveSaleOrderReq)

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"]
     Content type = application/json
             Body = {"successful":true,"code":"200","message":null,"data":1}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
{"successful":true,"code":"200","message":null,"data":1}


2.3 测试get类型的接口 (requestParam参数类型)

接口方法

@GetMapping(value = "/get-one")
public DataResponse<SaleOrder> getOne(@RequestParam(value = "saleOrderId") Long saleOrderId){
    SaleOrder saleOrder = saleOrderService.selectOne(saleOrderId);
    return DataResponse.ok(saleOrder);
}

测试方法

@Test
public void test2() throws Exception {
    MockHttpServletRequestBuilder requestBuilder =
        //请求路径,不需要带前面 servletContext部分
        MockMvcRequestBuilders.get("/saleOrder/get-one")
        // 设置请求参数
        .param("saleOrderId","1");
    MvcResult mvcResult = mockMvc.perform(requestBuilder)
        // 设置期望的结果,用 ResultMatcher 来表示
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andDo(MockMvcResultHandlers.print())
        .andReturn();
    String s = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
    System.out.println(s);

}

测试结果输出

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /saleOrder/get-one
       Parameters = {saleOrderId=[1]}
          Headers = []
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = com.fufulong.demo.mockdemo.controller.SaleOrderController
           Method = com.fufulong.demo.mockdemo.controller.SaleOrderController#getOne(Long)

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"]
     Content type = application/json
             Body = {"successful":true,"code":"200","message":null,"data":{"id":1,"saleOrderCode":"0001","customer":"小明"}}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
{"successful":true,"code":"200","message":null,"data":{"id":1,"saleOrderCode":"0001","customer":"小明"}}


2.4 测试get类型的接口 (path参数类型)

接口代码

@GetMapping(value = "/get-one/{saleOrderCode}/{id}")
public DataResponse<SaleOrder> getOneByCode(@PathVariable(value = "saleOrderCode") String saleOrderCode,
                                            @PathVariable(value = "id") Long id ){
    SaleOrder saleOrder = saleOrderService.getOneByCode(saleOrderCode,id);
    return DataResponse.ok(saleOrder);
}

测试方法

@Test
public void test3() throws Exception {
    String saleOrderCode = "0001";
    Long id = 1L;
    MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/saleOrder/get-one/{saleOrderCode}/{id}",saleOrderCode,id);
    MvcResult mvcResult = mockMvc.perform(requestBuilder)
        // 设置期望的结果,用 ResultMatcher 来表示
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andDo(MockMvcResultHandlers.print())
        .andReturn();
    String s = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
    System.out.println(s);
}

测试结果打印

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /saleOrder/get-one/0001/1
       Parameters = {}
          Headers = []
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = com.fufulong.demo.mockdemo.controller.SaleOrderController
           Method = com.fufulong.demo.mockdemo.controller.SaleOrderController#getOneByCode(String, Long)

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"]
     Content type = application/json
             Body = {"successful":true,"code":"200","message":null,"data":{"id":1,"saleOrderCode":"0001","customer":"小明"}}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
{"successful":true,"code":"200","message":null,"data":{"id":1,"saleOrderCode":"0001","customer":"小明"}}

2.5 测试上传文件

接口代码

@RequestMapping(value = "/uploadFile")
    public DataResponse<Void> uploadFile(@RequestParam(value = "file") MultipartFile file) throws IOException {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try{
            inputStream = file.getInputStream();
            outputStream = FileUtil.getOutputStream(new File(Objects.requireNonNull(file.getOriginalFilename())));
            IoUtil.copy(inputStream,outputStream);
        }finally {
            if (inputStream != null){
                inputStream.close();
            }
            if (outputStream != null){
                outputStream.close();
            }
        }

        return DataResponse.ok();
    }

测试代码

   @Test
    public void test4() throws Exception {
        File file = new File("C:\\Users\\付福龙\\Desktop\\兔子.jpeg");
        MockMultipartFile uploadFile = new MockMultipartFile("file", "兔子.jpeg", MediaType.IMAGE_JPEG_VALUE, new FileInputStream(file));
        MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.multipart("/saleOrder/uploadFile").file(uploadFile).contentType( MediaType.IMAGE_JPEG);
        MvcResult mvcResult = mockMvc.perform(requestBuilder)
                // 设置期望的结果,用 ResultMatcher 来表示
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }

测试结果

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /saleOrder/uploadFile
       Parameters = {}
          Headers = [Content-Type:"image/jpeg"]
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = com.fufulong.demo.mockdemo.controller.SaleOrderController
           Method = com.fufulong.demo.mockdemo.controller.SaleOrderController#uploadFile(MultipartFile)

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"]
     Content type = application/json
             Body = {"successful":true,"code":"200","message":null,"data":null}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []


mockMvc测试

3.mockMvc测试controller总结

  1. 先有业务接口方法
  2. 新建的测试类中,需要先初始化mockMvc对象
  3. 根据接口方法的类型不同(post/get/multipart等),选择构建不同的MockHttpServletRequestBuilder 对象, 在这里设置请求地址,注意地址是不需要 地址端口和servletContextpath前缀的,且需要注意用"/"开头. 然后设置 queryParam或者pathParam或者 content,还可以设置请求头等
  4. 调用mockMvc的方法,指定需要执行的请求, mockMvc.perform(MockHttpServletRequestBuilder )
  5. 然后设置断言,断言可以设置多个,使用方法 andExpect(ResultMatcher matcher) 设置, MockMvcResultMatchers中有许多常用的断言结果匹配器,可以使用
  6. 然后设置结果处理方法, 使用方法 ResultActions andDo(ResultHandler handler) 来处理,同样 MockMvcResultHandlers 总有需要封装好的静态方法可以用
  7. 然后 调用方法 MvcResult andReturn() ,此时才会真的执行接口, 得到 MvcResult 结果
  8. 可以使用 mvcResult.getResponse() 得到 MockHttpServletResponse 对象, 这个对象有许多请求结果的信息,比如状态,内容等, 可以根据这些内容写后续的验证代码

4. mockMvc测试重要API

41. MockMvcRequestBuilders

  • MockMvcRequestBuildersMockHttpServletRequestBuilder get(String urlTemplate, Object… uriVars)
    构建 get请求,设置请求地址,并且可以设置路径参数,注意如果要设置路径参数,参数值和类型必须要对应地址中参数名
  • MockHttpServletRequestBuilder post(String urlTemplate, Object… uriVars)
    构建 post 请求, 设置请求地址, post方法虽然也可以设置 pathParam和queryParam,但是不建议这样做.
  • MockMultipartHttpServletRequestBuilder multipart(String urlTemplate, Object… uriVars)
    构建 上传文件 请求, 设置上传文件的接口地址和路径参数等.

4.2 MockMvcRequestBuilder

  • MockHttpServletRequestBuilder param(String name, String… values)

    向请求参数map中添加非path类型的参数,也就是设置 queryParam的方法

  • MockHttpServletRequestBuilder header(String name, Object… values)
    向请求头map中添加请求头键值对

  • MockHttpServletRequestBuilder contentType(MediaType contentType)
    设置接口的参数内容的 contentType,如果业务接口是 @requestController 修饰的, contentType 一般是MediaType.APPLICATION_JSON

  • MockHttpServletRequestBuilder accept(MediaType… mediaTypes)
    设置接口的可以接收的返回数据的MediaType, 如果业务接口是 @requestController 修饰的, 一般是MediaType.APPLICATION_JSON

  • MockHttpServletRequestBuilder content(String content)
    设置requestBody的内容,一般是post请求的时候需要设置

4.3 ResultActions

  • ResultActions andExpect(ResultMatcher matcher)
    设置请求相关的断言,可以连续调用多次设置不同的断言.
  • ResultActions andDo(ResultHandler handler)
    设置对接口返回结果的处理方式
  • MvcResult andReturn()
    执行测试接口,得到结果,返回数据

4.4 MockMvcResultMatchers

  • StatusResultMatchers status(): 得到结果状态匹配器, 可以使用StatusResultMatchers 的方法断言请求状态,请求是否正常返回等.
  • ModelResultMatchers model(): 当请求不是rest风格的时候,返回接口结果中的数据model,可用进一步通过调用 ModelResultMatchers 的静态方法,判断返回的数据中的属性的值等
  • ViewResultMatchers view(): 当请求不是rest风格的时候,返回接口结果中的视图地址匹配器
  • JsonPathResultMatchers jsonPath(String expression, Object… args) / ResultMatcher jsonPath(String expression, Matcher<? super T> matcher) / ResultMatcher jsonPath(String expression, Matcher<? super T> matcher, Class targetType)
    当接口返回的数据是Json的形式,可以用这3个方法设置断言匹配器, 关于 jsoPath的设置,比较复杂,可以看官网地址: https://github.com/json-path/JsonPath

4.5 MockMvcResultHandlers

这里的方法主要是打印或者日志记录mock测试的主要信息,注意如果是打印到系统的控制台,使用的是系统的编码方式,如果是wIndows系统,就是ISO-8859-1,那么输出的中文就是乱码了.
mockMvc测试

4.6 MockHttpServletResponse

  • String getContentAsString(Charset fallbackCharset)
    把接口返回的数据内容转换成字符串形式,并指定了编码方式
上一篇:【文件】ota_from_target_files的python脚本


下一篇:Springboot MockMvc 单元测试 参数传值问题