如何使用 mockMvc 检查响应正文中的字符串

Posted

技术标签:

【中文标题】如何使用 mockMvc 检查响应正文中的字符串【英文标题】:How to check String in response body with mockMvc 【发布时间】:2013-08-22 13:50:21 【问题描述】:

我有简单的集成测试

@Test
public void shouldReturnErrorMessageToAdminWhenCreatingUserWithUsedUserName() throws Exception 
    mockMvc.perform(post("/api/users").header("Authorization", base64ForTestUser).contentType(MediaType.APPLICATION_JSON)
        .content("\"userName\":\"testUserDetails\",\"firstName\":\"xxx\",\"lastName\":\"xxx\",\"password\":\"xxx\""))
        .andDo(print())
        .andExpect(status().isBadRequest())
        .andExpect(?);

在最后一行我想将响应正文中收到的字符串与预期的字符串进行比较

作为回应,我得到:

MockHttpServletResponse:
          Status = 400
   Error message = null
         Headers = Content-Type=[application/json]
    Content type = application/json
            Body = "Username already taken"
   Forwarded URL = null
  Redirected URL = null

用 content()、body() 尝试了一些技巧,但没有奏效。

【问题讨论】:

作为建议,不应为"Username already taken" 之类的内容返回 400 状态代码。这应该更像是 409 冲突。 Thanx - 此测试的目标是指定此类内容。 【参考方案1】:

您可以调用andReturn() 并使用返回的MvcResult 对象将内容作为String 获取。

见下文:

MvcResult result = mockMvc.perform(post("/api/users").header("Authorization", base64ForTestUser).contentType(MediaType.APPLICATION_JSON)
            .content("\"userName\":\"testUserDetails\",\"firstName\":\"xxx\",\"lastName\":\"xxx\",\"password\":\"xxx\""))
            .andDo(MockMvcResultHandlers.print())
            .andExpect(status().isBadRequest())
            .andReturn();

String content = result.getResponse().getContentAsString();
// do what you will 

【讨论】:

【参考方案2】:

@Sotirios Delimanolis 回答完成了这项工作,但是我正在寻找比较这个 mockMvc 断言中的字符串

原来是这样

.andExpect(content().string("\"Username already taken - please try with different username\""));

当然我的断言失败了:

java.lang.AssertionError: Response content expected:
<"Username already taken - please try with different username"> but was:<"Something gone wrong">

因为:

  MockHttpServletResponse:
            Body = "Something gone wrong"

所以这证明它有效!

【讨论】:

以防万一有人像我一样收到带有动态 ID 的消息,知道 string() 方法也接受 hamcrest containsString 匹配器会很有帮助:.andExpect(content().string(containsString("\"Username already taken"); @TimBüthe,这是不正确的。如果您遇到这样的问题,您应该将其作为问题发布,因为这绝对不是预期的行为,也不是我在自己的代码中看到的行为。 请注意导入是org.hamcrest.Matchers.containsString() 我还使用了org.hamcrest.Matchers.equalToIgnoringWhiteSpace() 匹配器来忽略所有空白字符。也许这对某人有用的提示【参考方案3】:

Spring MockMvc 现在直接支持 JSON。所以你就说:

.andExpect(content().json("'message':'ok'"));

与字符串比较不同,它会说“缺少字段 xyz”或“消息预期 'ok' 得到 'nok'。

这个方法是在 Spring 4.1 中引入的。

【讨论】:

你能提供一个完整的例子吗?不需要ContentRequestMatchers 也支持此功能吗?【参考方案4】:

阅读这些答案,我可以看到很多与 Spring 4.x 版有关的内容,出于各种原因,我正在使用 3.2.0 版。所以像json这样的东西直接来自content()是不可能的。

我发现使用MockMvcResultMatchers.jsonPath 真的很简单而且很有效。这是一个测试 post 方法的示例。

此解决方案的好处是您仍然可以匹配属性,而不是依赖完整的 json 字符串比较。

(使用org.springframework.test.web.servlet.result.MockMvcResultMatchers

String expectedData = "some value";
mockMvc.perform(post("/endPoint")
                .contentType(MediaType.APPLICATION_JSON)
                .content(mockRequestBodyAsString.getBytes()))
                .andExpect(status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.data").value(expectedData));

请求正文只是一个 json 字符串,如果需要,您可以轻松地从真正的 json 模拟数据文件中加载它,但我没有在此处包含它,因为它会偏离问题。

实际返回的 json 应该是这样的:


    "data":"some value"

【讨论】:

为“.andExpect(MockMvcResultMatchers.jsonPath("$.data").value(expectedData))”点赞【参考方案5】:

取自spring的tutorial

mockMvc.perform(get("/" + userName + "/bookmarks/" 
    + this.bookmarkList.get(0).getId()))
    .andExpect(status().isOk())
    .andExpect(content().contentType(contentType))
    .andExpect(jsonPath("$.id", is(this.bookmarkList.get(0).getId().intValue())))
    .andExpect(jsonPath("$.uri", is("http://bookmark.com/1/" + userName)))
    .andExpect(jsonPath("$.description", is("A description")));

is 可从import static org.hamcrest.Matchers.*; 获得

jsonPath 可从import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 获得

jsonPath参考可以找到here

【讨论】:

我得到error: incompatible types: RequestMatcher cannot be converted to ResultMatcher .andExpect(content().contentType(contentType)) @IanVaughan MockMvcResultMatchers.content().contentType(contentType)【参考方案6】:

Spring security 的 @WithMockUser 和 hamcrest 的 containsString 匹配器提供了一个简单而优雅的解决方案:

@Test
@WithMockUser(roles = "USER")
public void loginWithRoleUserThenExpectUserSpecificContent() throws Exception 
    mockMvc.perform(get("/index"))
            .andExpect(status().isOk())
            .andExpect(content().string(containsString("This content is only shown to users.")));

More examples on github

【讨论】:

【参考方案7】:

这里有更优雅的方式

mockMvc.perform(post("/retrieve?page=1&countReg=999999")
            .header("Authorization", "Bearer " + validToken))
            .andExpect(status().isOk())
            .andExpect(content().string(containsString("regCount")));

【讨论】:

【参考方案8】:

这是一个如何解析 JSON 响应,甚至如何使用 JSON 形式的 bean 发送请求的示例:

  @Autowired
  protected MockMvc mvc;

  private static final ObjectMapper MAPPER = new ObjectMapper()
    .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
    .registerModule(new JavaTimeModule());

  public static String requestBody(Object request) 
    try 
      return MAPPER.writeValueAsString(request);
     catch (JsonProcessingException e) 
      throw new RuntimeException(e);
    
  

  public static <T> T parseResponse(MvcResult result, Class<T> responseClass) 
    try 
      String contentAsString = result.getResponse().getContentAsString();
      return MAPPER.readValue(contentAsString, responseClass);
     catch (IOException e) 
      throw new RuntimeException(e);
    
  

  @Test
  public void testUpdate() 
    Book book = new Book();
    book.setTitle("1984");
    book.setAuthor("Orwell");
    MvcResult requestResult = mvc.perform(post("http://example.com/book/")
      .contentType(MediaType.APPLICATION_JSON)
      .content(requestBody(book)))
      .andExpect(status().isOk())
      .andReturn();
    UpdateBookResponse updateBookResponse = parseResponse(requestResult, UpdateBookResponse.class);
    assertEquals("1984", updateBookResponse.getTitle());
    assertEquals("Orwell", updateBookResponse.getAuthor());
  

正如您在此处看到的,Book 是请求 DTO,UpdateBookResponse 是从 JSON 解析的响应对象。您可能想要更改 Jackson 的 ObjectMapper 配置。

【讨论】:

【参考方案9】:

一种可能的方法是简单地包含 gson 依赖项:

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
</dependency>

并解析值以进行验证:

@RunWith(SpringRunner.class)
@WebMvcTest(HelloController.class)
public class HelloControllerTest 

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private HelloService helloService;

    @Before
    public void before() 
        Mockito.when(helloService.message()).thenReturn("hello world!");
    

    @Test
    public void testMessage() throws Exception 
        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/"))
                .andExpect(status().isOk())
                .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON_VALUE))
                .andReturn();

        String responseBody = mvcResult.getResponse().getContentAsString();
        ResponseDto responseDto
                = new Gson().fromJson(responseBody, ResponseDto.class);
        Assertions.assertThat(responseDto.message).isEqualTo("hello world!");
    

【讨论】:

【参考方案10】:

另一种选择是:

when:

def response = mockMvc.perform(
            get('/path/to/api')
            .header("Content-Type", "application/json"))

then:

response.andExpect(status().isOk())
response.andReturn().getResponse().getContentAsString() == "what you expect"

【讨论】:

【参考方案11】:

您可以使用getContentAsString 方法将响应数据作为字符串获取。

    String payload = "....";
    String apiToTest = "....";
    
    MvcResult mvcResult = mockMvc.
                perform(post(apiToTest).
                content(payload).
                contentType(MediaType.APPLICATION_JSON)).
                andReturn();
    
    String responseData = mvcResult.getResponse().getContentAsString();

您可以参考此link 进行测试应用。

【讨论】:

【参考方案12】:
String body = mockMvc.perform(bla... bla).andReturn().getResolvedException().getMessage()

这应该为您提供响应的正文。在您的情况下,“用户名已被占用”。

【讨论】:

哪里有解释?它是必需的,或者您可以在评论中给出这种类型的答案

以上是关于如何使用 mockMvc 检查响应正文中的字符串的主要内容,如果未能解决你的问题,请参考以下文章

如何使用mockMvc检查响应体中的JSON

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

MockMVC的使用

使用mockmvc测试如何处理无序数组

是否有任何适当的匹配器来解析和比较来自 MockMvc 的 Json 响应中的 LocalDateTime 字段

如何为内容类型的响应正文提供示例值:Swagger 中的 text/html(使用 dredd 进行测试)