springsecurity单元测试

Published on 2022-01-13 15:58 in 分类: 随笔 with 狂盗一枝梅
分类: 随笔

一、引入依赖

首先引入顶层依赖管理

 <!-- 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


#java #单元测试
目录