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服務