SpringTest 框架 MVC 场景初体验

SpringTest 框架 MVC 场景初体验

官方资料

官方文档;[官方代码实例](spring-framework/spring-test/src/test/java/org/springframework/test/web/servlet/samples at main · spring-projects/spring-framework · GitHub);

这篇博客基本上相当于官方文档的中文翻译,同时还增加了自己的思考,值得跟官方文档对照着学习:https://segmentfault.com/a/1190000024443851

以后有需要有时间再来仔细地看这些资料,


服务器端测试

这是我们主要的使用场景,直接先看代码,再梳理一下 API

示例代码

引入 pom 依赖

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>${junit.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>${junit.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
</dependency>

被测试的控制器

@Controller
@RequestMapping("/testParams")
public class TestParamsController {

    @RequestMapping("/testSpringMVCAPI")
    public String testSpringMVCAPI(String username, String password) {
        System.out.println(username + password);
        return "target";
    }

    // 注意:此时Form表单的post请求无效,会报格式不支持,因为form表单提交的请求的请求体格式是application/x-www-form-urlencoded,HttpMessageConverter不知道hi,
    // 需要发起消息体格式为application/json的post请求
    @RequestMapping("/testSpringMVCAPIRequestBody")
    public String testSpringMVCAPIPOJOMapping(@RequestBody User body2user, User user) {
        System.out.println(body2user.toString());
        System.out.println(user.toString());
        return "target";
    }

}

MVCMock 测试代码

package xyz.xiashuo.springmvchttpparams.controllertest;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

@SpringJUnitWebConfig(locations = "classpath:springMVC.xml")
public class SpringMVCAPITest {
    MockMvc mockMvc;

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

    @Test
    void testSpringMVCAPI() throws Exception {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("username", "xiashuo");
        map.add("password", "7879787");
        this.mockMvc.perform(post("/testParams/testSpringMVCAPI")
                        .queryParams(map))
                .andExpect(status().isOk());
        // 模拟表单发起的请求
        //this.mockMvc.perform(post("/testParams/testSpringMVCAPI")
        //                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
        //                .content("username=xiashuo&password=7879787")
        //                )
        //        .andExpect(status().isOk());
        // 模拟ajax发起的json请求,请求进入控制器方法,但是控制器方法无法获取json中的参数
        //this.mockMvc.perform(post("/testParams/testSpringMVCAPI")
        //                .contentType(MediaType.APPLICATION_JSON)
        //                .content("{\"username\":\"xiashuo\",\"password\":7879787}")
        //        )
        //        .andExpect(status().isOk());
    }

    @Test
    void testSpringMVCAPIRequestBody() throws Exception {
        // json格式的消息体,可以到达控制器方法
        this.mockMvc.perform(
                        post("/testParams/testSpringMVCAPIRequestBody")
                                .contentType(MediaType.APPLICATION_JSON)
                                .content("{ \"username\": \"sd\", \"gender\": \"男\", \"age\": 12 }\n")
                )
                .andExpect(status().isOk())
                .andExpect(view().name("target"));
        // form格式的消息体,无法到达控制器方法,报错不支持的格式,415
        //MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        //map.add("username", "xiashuo");
        //map.add("password", "123456798");
        //map.add("gender", "男");
        //this.mockMvc.perform(
        //                post("/testParams/testSpringMVCAPIRequestBody")
        //                        .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
        //                        .content("")
        //        )
        //        .andExpect(status().isUnsupportedMediaType());
    }

}

API 梳理

我们直接来梳理一下示例代码中使用的主要的 API。

发起请求

发起模拟的 http 请求的方式是 MockMvc.perform(),需要传入 RequestBuilder 类型的参数,MockMvcRequestBuilders 中提供了 HTTP 请求方法对应的返回 RequestBuilder 的方法,比如常用的 put()、delete()、post()、get(),参数为请求路径,基本上所有 HTTP 请求方法都有对应的 Java 方法与之对应,其中最常用的还是 post() 和 get(),

返回的 RequestBuilder 中最常见的实现类型有两个 MockHttpServletRequestBuilder 和 MockMultipartHttpServletRequestBuilder

验证请求结果

发送了请求,我们就得验证这个请求返回的结果是不是我们期望的结果,MockMvc.perform() 方法返回的类型是 ResultActions,我们调用其 andExpect 方法进行判断,andExpect 方法参数为 ResultMatcher,MockMvcResultMatchers 中提供了各种 ResultMatcher 模板,通过传入参数即可返回特定的 ResultMatcher 对象,我们列举几个常用的:

MockMvcResultMatchers 中还有其他的 ResultMatcher 类,应有尽有,绝对够用了。

大部分的时候,我们都想在模拟请求执行完之后做一点事情,比如打印日志,看看模拟请求的具体情况,这个时候,可以调用 ResultActions 的 andDo 方法,andDo 方法的参数是 ResultHandler,MockMvcResultHandlers 中提供了各种 ResultHandler 模板,比如 print 方法(在控制台输出模拟请求的详细信息)和 log 方法(记录模拟请求的信息到测试日志中)

this.mockMvc.perform(
                post("/testParams/testSpringMVCAPIRequestBody")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{ \"username\": \"sd\", \"gender\": \"男\", \"age\": 12 }\n")
        )
        .andExpect(status().isOk())
        .andExpect(view().name("target")).andDo(print());

输出日志,可以很清晰的看到模拟请求和模拟响应,模拟响应是一个 HTML 页面。

在某些情况下,你可能希望直接访问请求的结果并验证一些 ResultMatcher 无法验证的内容,或者你需要拿这个请求返回结果进行进一步的操作,比如 asyncDispatch 异步请求。可以调用 ResultActions 的 andReturn 方法,直接在所有 andExpect 和 andDo 之后添加 .andReturn() 来实现,如以下示例所示:

MvcResult target = this.mockMvc.perform(
                post("/testParams/testSpringMVCAPIRequestBody")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{ \"username\": \"sd\", \"gender\": \"男\", \"age\": 12 }\n")
        )
        .andExpect(status().isOk())
        .andExpect(view().name("target")).andReturn();
// 这里输出的就是target视图解析出来的HTML页面
System.out.println(target.getResponse().getContentAsString());

输出页面

当然了,这里只是打个样,如果你真的想要输出视图的 html,那还是通过 andDo(print()) 方便些。


使用前端发起请求 -- 和 HtmlUnit 的集成

HtmlUnit官网

HtmlUnit 是一个“面向 Java 程序的无 gui 浏览器”。它对 HTML 文档进行建模,并提供了一个 API,允许您调用页面、填写表单、点击链接等。就像你在“普通”浏览器中做的那样。

意思就是允许你在 Java 代码中模拟 hmtl 页面填写表格的操作。主要用于测试框架,相当于我们可以模拟普通使用场景的手点操作,这样的跟实际使用一模一样的测试,才是真测试,

这里目前我们用不到,先不学了,TODO

注意