MockMVC的使用

Posted LonZyuan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MockMVC的使用相关的知识,希望对你有一定的参考价值。

简介

MockMvc是一种基于Java和JUnit的测试框架,旨在测试Spring MVC应用程序的控制器层。它可以帮助我们模拟HTTP请求,检查响应值,以及发送表单数据和文件等。 MockMvc可以与其他测试组件(如JUnit和Hamcrest)结合使用,以模拟REST API端点或用户界面的操作。

Get请求

简单示例

创建一个简单的Controller:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/mockMcv")
public class MockMvcController 

    @GetMapping("/first/connect")
    public String testConnect() 
        String result = "Hello MockMVC";
        return result;
    

创建测试类,使用MockMVC

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
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;
​
​
// webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT:
// 指示Spring Boot在随机且可用的端口上启动Web服务器,避免端口冲突
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
// 自动配置MockMvc和Spring IOC容器
@AutoConfigureMockMvc
@RunWith(SpringRunner.class)
public class MockMvcControllerTest 
​
    @Autowired
    private MockMvc mockMvc;
​
    @Test
    public void testConnect() throws Exception 
        MvcResult mvcResult = mockMvc.perform(
                // Get请求,url为:/mockMcv/first/connect
                MockMvcRequestBuilders.get("/mockMcv/first/connect"))
                // 检查响应状态码是否为200
                .andExpect(MockMvcResultMatchers.status().isOk())
                // 返回一个MvcResult回调对象
                .andReturn();
        // 获取响应文本
        String content = mvcResult.getResponse().getContentAsString();
        // 通过断言来验证返回是否正确
        Assert.assertTrue(content.equals("Hello MockMVC"));
    
​

直接启动Debug启动,请求成功:

注意点

上述示例中需要注意,@SpringBootTest注解需要括号中的内容

去掉@SpringBootTest括号中的内容,运行结果:

 

原因:@SpringBootTest注释用于在测试与Spring Boot应用程序的集成中启用Spring上下文,如果使用默认配置,那就是Mock模式,即加载一个伪造的Web环境,其中一些内容(例如HTTP会话和重定向)不实际触发,而只是返回一个默认值。

所以在测试期间尝试模拟HTTP请求并使用不实际存在的Session时,可能会从该会话对象中出现NullPointerException。

Get带参数

controller中新加一个带参数的Get请求:

@GetMapping("/connect/withParam")
public String testConnectWithParam(@RequestParam("id") Long id, @RequestParam("name") String name) 
    String result = "id:" + id + ",name:" + name;
    return result;

测试类新增对应的测试方法:

@Test
public void testConnectWithParam() throws Exception 
    MvcResult mvcResult = mockMvc.perform(
                MockMvcRequestBuilders.get("/mockMcv/connect/withParam")
                        // 通过param()存放各个请求参数
                        .param("id", "1")
                        .param("name", "MockMVC"))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andReturn();
    String content = mvcResult.getResponse().getContentAsString();
    Assert.assertTrue(content.contains("MockMVC"));

Debug启动,请求成功:

Post请求

示例

controller中新加一个带Body的Post请求:

@PostMapping("/connect/withBody")
public String testConnectWithBody(@RequestBody Map<String,String> requestBody) 
    String result = JSONArray.toJSONString(requestBody);
    return result;

新增一个Java对象存放值:

import lombok.Data;
import java.io.Serializable;
​
@Data
public class MockMVCInfo implements Serializable 
    private static final Long serializableID = -1L;
    private Long id;
    private String name;

测试类新增对应的测试方法:

@Test
public void testConnectWithBody() throws Exception 
    MockMVCInfo mockMVCInfo = new MockMVCInfo();
    mockMVCInfo.setId(3L);
    mockMVCInfo.setName("MockMVC");
    MvcResult mvcResult = mockMvc.perform(
            MockMvcRequestBuilders.post("/mockMcv/connect/withBody")
                    // 指定 header中的 Content-Type为 application/json
                    .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                    // 通过content()存放Body的Json
                    .content(JSON.toJSONString(mockMVCInfo)))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andReturn();
    String content = mvcResult.getResponse().getContentAsString();
    Assert.assertTrue(content.contains("MockMVC"));

Debug启动,请求成功:

注意点

1.通过RequestBody传递值,就得使用.content()方法存放Json

2.Body中使用Json,需要在header中指定Content-Type为application/json,参考Postman:

 

MockMVC - 如何使用 org.hamcrest.Matcher 在春季安全集成测试中检查 JWT 令牌的内容

【中文标题】MockMVC - 如何使用 org.hamcrest.Matcher 在春季安全集成测试中检查 JWT 令牌的内容【英文标题】:MockMVC - How to check the content of a JWT token in a spring security integration test with org.hamcrest.Matcher 【发布时间】:2019-05-04 20:50:08 【问题描述】:

我会从 MockMvc 请求中获取 JWT 令牌作为响应。我想检查一下这个回复的内容:

mockMvc.perform(post("/authorize")
        .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeEmailAndPassword("test1@app.com", "1111"))
        .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
        .params(params)
        .accept(MediaType.APPLICATION_JSON))
    .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
    .andDo(print())
    .andExpect(status().isOk())
;

结果将是:


    "id_token": "............(long Base64 string)"

当我们使用 JWT.io 解码令牌时,我们会看到:


  "sub": "cc15a160-2d62-4091-b89a-117e77346a58",
  "nbf": 1543846725,
  "auth_level": "trusted",
  "iss": "http://localhost:9090/",
  "exp": 1543847724,
  "iat": 1543846725,
  "nonce": "random_string",
  "jti": "64b8b6e3-5cd0-4242-bcea-2c5d498d64c1"

一切都很好,但我想做这样的事情:

.andExpect(jsonPath("$.id_token", Matchers.not(null)))
.andExpect(decodeJWT(jsonPath("$.id_token")).getValueOf("nonce"), Matchers.is("random_string"));

我该怎么做?

【问题讨论】:

【参考方案1】:

好吧,我自己找到了答案...基本上,org.hamcrest.Matcher 是不可能的,但我们可以将响应分成几部分并将它们映射到 DTO。

首先,我做一些状态和基本检查,然后将响应返回为MvcResult

MvcResult result = mockMvc.perform(post("/authorize")
                .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeEmailAndPassword("test1@app.com", "1111"))
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(content)
                .accept(MediaType.APPLICATION_JSON))
            .andDo(print())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id_token", Matchers.notNullValue()))
            .andReturn();

然后,我为 Jackson 反序列化创建了一些 DTO:(记得创建类 not 作为内部类,因为 Jackson 会抱怨“只能使用内部非静态类的无参数构造函数")

@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
class TokenResponseDTO implements Serializable 
    //@JsonProperty("id_token")
    private String idToken;


@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
class JWTPayloadDTO implements Serializable 
    private String aud;
    private String sub;
    private String nbf;
    private String authLevel;
    private String iss;
    private Long exp;
    private Long iat;
    private String nonce;
    private String jti;

终于,JWT 令牌解析比我想象的要容易得多:

String token = mapper.readValue(result.getResponse().getContentAsString(), TokenResponseDTO.class).getIdToken();
JWSObject jwsObject = JWSObject.parse(token);
JWTPayloadDTO payload = mapper.readValue(jwsObject.getPayload().toString(), JWTPayloadDTO.class);

Assert.assertEquals("random_string", payload.getNonce());
... // other checks

【讨论】:

谢谢。你的回答对我很有帮助。

以上是关于MockMVC的使用的主要内容,如果未能解决你的问题,请参考以下文章

MockMVC - 如何使用 org.hamcrest.Matcher 在春季安全集成测试中检查 JWT 令牌的内容

使用 MockMvc 测试重定向 URL 的 HTTP 状态码

在 Spring Boot 1.4 MVC 测试中使用 @WebMvcTest 设置 MockMvc

使用 MockMvc 时是不是可以添加断言消息?

如何正确使用 MockMvc 在 Junit 中测试 post 方法?

使用 Spring MockMVC 测试 Spring 的 @RequestBody