使用MockMvc测试Restful接口
1. MockMvc
MockMvc
是由spring-test
包提供,实现了对Http
请求的模拟,能够直接使用网络的形式,转换到Controller
的调用,使得测试速度快、不依赖网络环境。同时提供了一套验证的工具,结果的验证十分方便。
接口MockMvcBuilder
,提供一个唯一的build
方法,用来构造MockMvc
。主要有两个实现:StandaloneMockMvcBuilder
和DefaultMockMvcBuilder
,分别对应两种测试方式,即独立安装和集成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. 参考
Java后端安全开发Spring Security开发REST服务