使用MockMvc测试Restful接口

1. MockMvc

MockMvc是由spring-test包提供,实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,使得测试速度快、不依赖网络环境。同时提供了一套验证的工具,结果的验证十分方便。

接口MockMvcBuilder,提供一个唯一的build方法,用来构造MockMvc。主要有两个实现:StandaloneMockMvcBuilderDefaultMockMvcBuilder,分别对应两种测试方式,即独立安装和集成Web环境测试(并不会集成真正的web环境,而是通过相应的Mock API进行模拟测试,无须启动服务器)。MockMvcBuilders提供了对应的创建方法standaloneSetup方法和webAppContextSetup方法,在使用时直接调用即可。

2. 引入jar包

创建SpringBoot项目中默认引入的spring-boot-starter-test间接引入了spring-test,因此无需再额外引入jar包。最终的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.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>mockdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mockdemo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


3. 创建业务bean和控制器

User Bean

package com.example.mockdemo.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import javax.validation.constraints.Past;
import java.util.Date;

/**
 * @author john
 * @date 2020/4/9 - 18:01
 */
@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;
    private String username;
    private String password;
    @Past(message = "生日必须是过去的时间")
    private Date birthday;
}

FileInfo Dto

package com.example.mockdemo.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * @author john
 * @date 2020/4/10 - 10:00
 */
@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FileInfo {
    private String path;
}

UserQueryCondition Dto

package com.example.mockdemo.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserQueryCondition {
    private String username;
    private int age;
    private int ageTo;
    private String xxx;
}

FileController

package com.example.mockdemo.controller;

import com.example.mockdemo.dto.FileInfo;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * @author john
 * @date 2020/4/10 - 9:54
 */
@RestController
@RequestMapping("/file")
public class FileController {

    private String folder = "D:\test";

    @PostMapping
    //文件上传
    public FileInfo upload(MultipartFile file) throws Exception {

        System.out.println(file.getName());
        System.out.println(file.getOriginalFilename());
        System.out.println(file.getSize());

        File localFile = new File(folder, "aa.txt");

        file.transferTo(localFile);

        return new FileInfo(localFile.getAbsolutePath());
    }

    @GetMapping("/{id}")
    //文件下载
    public void download(@PathVariable String id, HttpServletRequest request, HttpServletResponse response) throws Exception {

        try (InputStream inputStream = new FileInputStream(new File(folder, id + ".txt"));
             OutputStream outputStream = response.getOutputStream();) {

            response.setContentType("application/x-download");
            response.addHeader("Content-Disposition", "attachment;filename=test.txt");

            IOUtils.copy(inputStream, outputStream);
            outputStream.flush();
        }

    }

}

UserController

package com.example.mockdemo.controller;

import com.example.mockdemo.domain.User;
import com.example.mockdemo.dto.UserQueryCondition;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author john
 * @date 2020/4/9 - 18:00
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @PostMapping
    //创建用户
    public User create(@RequestBody User user) {
        user.setId(1);
        return user;
    }

    @PutMapping("/{id:\d+}")
    //更新用户
    public User update(@RequestBody User user) {
        user.setId(1);
        return user;
    }

    @DeleteMapping("/{id:\d+}")
    //删除用户
    public void delete(@PathVariable String id) {
        System.out.println("用户id为" + id + "删除成功");
    }

    @GetMapping
    //查询用户
    public List<User> query(UserQueryCondition condition, @PageableDefault(page = 2, size = 17, sort = "username,asc") Pageable pageable) {
        System.out.println(pageable.getPageSize());
        System.out.println(pageable.getPageNumber());
        System.out.println(pageable.getSort());
        List<User> users = new ArrayList<>();
        users.add(new User(1, "john", "", new Date()));
        users.add(new User(2, "tom", "", new Date()));
        users.add(new User(3, "jim", "", new Date()));
        return users;
    }

    @GetMapping("/{id:\d+}")
    //获取具体用户信息
    public User getInfo(@PathVariable String id) {
        System.out.println("进入getInfo服务");
        User user = new User();
        user.setUsername("tom");
        return user;
    }
}

4. 编写测试类

package com.example.mockdemo;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringRunner.class)
@SpringBootTest
class MockdemoApplicationTests {

    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext wac;

    @BeforeEach
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    //测试删除用户接口
    public void whenDeleteSuccess() throws Exception {
        mockMvc.perform(delete("/user/1")
                .contentType(MediaType.APPLICATION_JSON_VALUE))
                .andExpect(status().isOk());
    }

    @Test
    //测试更新用户数据
    public void whenUpdateSuccess() throws Exception {

        Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        System.out.println(date.getTime());
        String content = "{"id":"1", "username":"tom","password":null,"birthday":" + date.getTime() + "}";
        String reuslt = mockMvc.perform(put("/user/1").contentType(MediaType.APPLICATION_JSON_VALUE)
                .content(content))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
                .andExpect(jsonPath("$.id").value("1"))
                .andReturn().getResponse().getContentAsString();

        System.out.println(reuslt);
    }

    @Test
    //测试创建用户成功
    public void whenCreateSuccess() throws Exception {

        Date date = new Date();
        System.out.println(date.getTime());
        String content = "{"username":"tom","password":null,"birthday":" + date.getTime() + "}";
        String reuslt = mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON_VALUE)
                .content(content))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value("1"))
                .andReturn().getResponse().getContentAsString();

        System.out.println(reuslt);
    }

    @Test
    //测试获取用户信息失败
    public void whenGetInfoFail() throws Exception {
        mockMvc.perform(get("/user/a")
                .contentType(MediaType.APPLICATION_JSON_VALUE))
                .andExpect(status().is4xxClientError());
    }

    @Test
    //测试获取用户信息成功
    public void whenGetInfoSuccess() throws Exception {
        String result = mockMvc.perform(get("/user/1")
                .contentType(MediaType.APPLICATION_JSON_VALUE))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.username").value("tom"))
                .andReturn().getResponse().getContentAsString();

        System.out.println(result);
    }

    @Test
    //测试查询用户数据接口
    public void whenQuerySuccess() throws Exception {
        String result = mockMvc.perform(
                get("/user").param("username", "jojo")
                        .param("age", "18")
                        .param("ageTo", "60")
                        .param("xxx", "yyy")
                        .param("size", "15")
                        .param("page", "3")
                        .param("sort", "age,desc")
                        .contentType(MediaType.APPLICATION_JSON_VALUE))
                .andExpect(status().isOk()).
                        andExpect(jsonPath("$.length()").value(3))
                .andReturn().getResponse().getContentAsString();

        System.out.println(result);
    }

    @Test
    //测试文件上传
    public void whenUploadSuccess() throws Exception {
        MockMultipartFile file = new MockMultipartFile("file", "test.txt", "multipart/form-data", "hello upload".getBytes("UTF-8"));
        String result = mockMvc.perform(multipart("/file").file(file))
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();
        System.out.println(result);
    }

}

5. 代码分享

微云下载

6. 参考

Server-Side Tests

Spring MVC测试框架详解——服务端测试

官方示例

Java后端安全开发Spring Security开发REST服务

原文地址:https://www.cnblogs.com/ifme/p/12671773.html