如何检查字符串在响应体与mockMvc

我有简单的积分测试

@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()的一些技巧,但都不起作用。

456595 次浏览

你可以调用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

@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"

所以这就是它有效的证明!

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

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

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

该方法是在Spring 4.1中引入的。

String body = mockMvc.perform(bla... bla).andReturn().getResolvedException().getMessage()

这将为您提供响应的主体。“用户名已被占用”在你的情况下。

阅读这些答案,我可以看到很多与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"
}

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.")));
}

更多的例子在github

取自spring的教程

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引用可以在在这里中找到

这里有一种更优雅的方式

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

下面是一个例子,如何解析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配置。

你可以使用getContentAsString方法来获取字符串形式的响应数据。

    String payload = "....";
String apiToTest = "....";
    

MvcResult mvcResult = mockMvc.
perform(post(apiToTest).
content(payload).
contentType(MediaType.APPLICATION_JSON)).
andReturn();
    

String responseData = mvcResult.getResponse().getContentAsString();

你可以在测试应用中引用这个链接

一种可能的方法是简单地包含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!");
}
}

另一种选择是:

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"

另一个例子是:

.andExpect(jsonPath("$").value(containsString("You have successfully deleted")));

身体反应:

Body = You have successfully deleted a [Object] with ID: 1

这是一种更适合生产的方式,如果你可能有大的json响应,那么你不必用json字符串来混乱你的测试文件,只需从静态资源文件夹加载它们并直接断言它们。

  @Test
@DisplayName("invalid fields")
void invalidfields() throws Exception {


String request = getResourceFileAsString("test-data/http-request/invalid-fields.json");
String response_file_path = "test-data/http-response/error-messages/invalid-fields.json";
String expected_response = getResourceFileAsString(response_file_path);


mockMvc.perform(evaluateRulesOnData(TRACKING_ID.toString(), request))
.andExpect(status().isBadRequest())
.andExpect(content().json(expected_response));
}

从类路径加载测试文件的助手函数

  public static String getResourceFileAsString(String fileName) throws IOException {
Resource resource = new ClassPathResource(fileName);
File file = resource.getFile();
return new String(Files.readAllBytes(file.toPath()));
}

预期的响应有一个包含列表中许多元素的数组,这些元素在每次测试运行期间都是随机顺序匹配的。