一、引入依赖
首先引入顶层依赖管理
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
之后引入以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
二、单元测试
通用单元测试父类
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Objects;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
/**
* @author kdyzm
* @date 2021/12/20
*/
@Slf4j
@RunWith(SpringRunner.class)
@WebAppConfiguration
@Ignore
public class BaseControllerTest {
@Autowired
protected WebApplicationContext wac;
protected static MockMvc mvc;
protected static MockHttpSession session;
private File parentExport;
/**
* 变量token
*/
protected String getToken() {
return "";
}
@Before
public void setupMockMvc() throws Exception {
if (Objects.nonNull(mvc) && Objects.nonNull(session)) {
log.info("已经初始化");
return;
}
mvc = MockMvcBuilders.webAppContextSetup(wac).
apply(springSecurity())
.build(); //初始化MockMvc对象
session = new MockHttpSession();
parentExport = new File("export");
if (!parentExport.exists()) {
parentExport.mkdir();
}
}
/**
* 通用GET请求
*
* @param urlTemplate 请求地址
* @param typeReference 结果类型
* @param <R> 泛型
* @return 反序列化后的结果
* @throws Exception 异常
*/
protected <R> R doGet(String urlTemplate, TypeReference<R> typeReference) throws Exception {
return JsonUtils.read(this.doGetString(urlTemplate), typeReference);
}
/**
* GET请求String
*
* @param urlTemplate 请求地址
* @return 结果string
* @throws Exception 异常
*/
protected String doGetString(String urlTemplate) throws Exception {
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(urlTemplate)
.accept(MediaType.APPLICATION_JSON_UTF8)
.header(HttpHeaders.AUTHORIZATION, this.getToken())
.contentType(MediaType.APPLICATION_JSON)
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
return mvcResult.getResponse().getContentAsString();
}
/**
* 通用POST请求
*
* @param req 请求体
* @param urlTemplate 请求地址
* @param typeReference 返回结果类型
* @param <R> 出参泛型
* @param <T> 入参泛型
* @return 反序列化后的结果
* @throws Exception 异常
*/
protected <R, T> R doPost(T req, String urlTemplate, TypeReference<R> typeReference) throws Exception {
return JsonUtils.read(doPostString(req, urlTemplate), typeReference);
}
/**
* 通用POST请求String
*
* @param req 请求体
* @param urlTemplate 请求地址
* @param <T> 请求泛型
* @return 请求结果String
* @throws Exception 异常
*/
protected <T> String doPostString(T req, String urlTemplate) throws Exception {
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.post(urlTemplate)
.accept(MediaType.APPLICATION_JSON_UTF8)
.header(HttpHeaders.AUTHORIZATION, this.getToken())
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtils.toString(req))
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
return mvcResult.getResponse().getContentAsString();
}
/**
* 通用文件导出
*
* @param req 请求
* @param urlTemplate 地址
* @param <T> 请求类型
* @return 下载的文件
* @throws Exception 异常
*/
protected <T> File doExport(T req, String urlTemplate, String fileName) throws Exception {
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.post(urlTemplate)
.accept(MediaType.APPLICATION_JSON_UTF8)
.characterEncoding("UTF-8")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtils.toString(req))
.header(HttpHeaders.AUTHORIZATION, getToken())
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
byte[] contentAsByteArray = mvcResult.getResponse().getContentAsByteArray();
File file = new File(parentExport, fileName);
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(contentAsByteArray);
return file;
}
/**
* 输出漂亮的JSON格式的字符串
*
* @param obj 入参
* @param <T> 入参泛型
*/
protected <T> void outputPretty(T obj) {
log.info(JsonUtils.toPrettyString(obj));
}
}
之后业务上的单元测试只需要如此使用
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
/**
* @author kdyzm
* @date 2022/1/13
* @see SystemMenuController
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class SysMenuControllerTest extends BaseControllerTest {
/**
* @see SystemMenuController#getRouters()
*/
@Test
public void getRoutersTest() throws Exception {
WrapperResult<List<RouterVo>> listWrapperResult = this.doGet(
"/sys/menu/routers",
new TypeReference<WrapperResult<List<RouterVo>>>() {
});
this.outputPretty(listWrapperResult);
}
}
可以看到,代码非常简洁高效。
三、可能遇到的问题
1、spring security配置未生效
比如spring security的自定义过滤器在单元测试运行的时候未执行,可能是MockMvc未配置好,正确的配置方式:
mvc = MockMvcBuilders.webAppContextSetup(wac).
apply(springSecurity())
.build(); //初始化MockMvc对象
注意这里的springSecurity()方法,是静态导入的
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
2、中文乱码
@RequestMapping(value = "/sys/menu",produces = "application/json; charset=utf-8")
或者
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.post(urlTemplate)
.accept(MediaType.APPLICATION_JSON_UTF8)
.header(HttpHeaders.AUTHORIZATION, this.getToken())
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtils.toString(req))
.session(session)
)
这里的.accept(MediaType.APPLICATION_JSON_UTF8)
是关键,虽然标记了已过时,但是在这个场景下是可以继续使用的
四、参考文档
https://www.baeldung.com/spring-security-integration-tests
https://www.itread01.com/p/833625.html
注意:本文归作者所有,未经作者允许,不得转载