Cómo verificar cadenas en el cuerpo de respuesta con mockMvc

243

Tengo una prueba de integración simple

@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(?);
}

En la última línea, quiero comparar la cadena recibida en el cuerpo de la respuesta con la cadena esperada

Y en respuesta obtengo:

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

Intenté algunos trucos con content (), body () pero nada funcionó.

pbaranski
fuente
19
Solo como consejo, el código de estado 400 no debe ser devuelto por algo así "Username already taken". Eso debería ser más de un conflicto 409.
Sotirios Delimanolis
Gracias: el objetivo de esta prueba es especificar tales cosas.
pbaranski

Respuestas:

356

Puede llamar andReturn()y usar el MvcResultobjeto devuelto para obtener el contenido como a String.

Vea abajo:

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
fuente
77
@ TimBüthe ¿Puedes aclarar? A @RestControllerindica que todos los métodos de manejo están implícitamente anotados con @ResponseBody. Esto significa que Spring usará a HttpMessageConverterpara serializar el valor de retorno del controlador y escribirlo en la respuesta. Puedes conseguir mucho el cuerpo content().
Sotirios Delimanolis
55
@SotiriosDelimanolis es correcto ... Estoy mirando en este momento el JSON devuelto por el getContentAsString()que vino de mi @RestControllercontrolador anotado.
Paul
Encontré lo que estaba buscando en el mensaje de error:result.getResponse().getErrorMessage()
whistling_marmot
andReturn () está devolviendo un valor nulo
Giriraj
@Giriraj andReturndevuelve un MvcResult, como se especifica en el javadoc aquí .
Sotirios Delimanolis
105

La respuesta de @Sotirios Delimanolis hace el trabajo, sin embargo, estaba buscando comparar cadenas dentro de esta afirmación de mockMvc

Asi que aqui esta

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

Por supuesto, mi afirmación falla:

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

porque:

  MockHttpServletResponse:
            Body = "Something gone wrong"

¡Entonces esta es una prueba de que funciona!

pbaranski
fuente
17
En caso de que alguien tenga mensajes con ID dinámicas, como lo hice yo, es útil saber que el método string () también acepta un Hamcrest contiene String matcher:.andExpect(content().string(containsString("\"Username already taken");
molholm
44
@ TimBüthe, eso es incorrecto. Si tiene un problema así, debe publicarlo como una pregunta porque definitivamente no es el comportamiento esperado ni es el comportamiento que he presenciado en mi propio código.
Paul
2
Solo tenga en cuenta que la importación es org.hamcrest.Matchers.containsString().
membersound
También usé org.hamcrest.Matchers.equalToIgnoringWhiteSpace()matcher para ignorar todos los caracteres de espacio en blanco. Tal vez sea un consejo útil para alguien
Iwo Kucharski
66

Spring MockMvc ahora tiene soporte directo para JSON. Entonces solo dices:

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

y a diferencia de la comparación de cadenas, dirá algo como "campo faltante xyz" o "mensaje Esperado 'ok' consiguió 'nok'.

Este método fue introducido en Spring 4.1.

vertti
fuente
2
¿podrías dar un ejemplo completo? ¿No es necesario ContentRequestMatchersque también admita esta función?
Zarathustra
49

Al leer estas respuestas, puedo ver muchas cosas relacionadas con Spring versión 4.x, estoy usando la versión 3.2.0 por varias razones. Entonces, cosas como el soporte de json directamente desde el content()no es posible.

Descubrí que usar MockMvcResultMatchers.jsonPathes realmente fácil y funciona muy bien. Aquí hay un ejemplo que prueba un método de publicación.

La ventaja con esta solución es que todavía está haciendo coincidir los atributos, no confiando en comparaciones completas de cadenas json.

(Usando 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));

El cuerpo de la solicitud era solo una cadena json, que puede cargar fácilmente desde un archivo de datos simulado json real si lo desea, pero no lo incluí aquí ya que se habría desviado de la pregunta.

El json real devuelto se habría visto así:

{
    "data":"some value"
}
Jeremy
fuente
felicitaciones por ".andExpect (MockMvcResultMatchers.jsonPath (" $. data "). value (
pectedData
28

Tomado del tutorial de primavera

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 está disponible desde import static org.hamcrest.Matchers.*;

jsonPath está disponible desde import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

y jsonPathreferencia se puede encontrar aquí

usuario2829759
fuente
1
Llego error: incompatible types: RequestMatcher cannot be converted to ResultMatcher por.andExpect(content().contentType(contentType))
Ian Vaughan
@IanVaughan MockMvcResultMatchers.content (). ContentType (contentType)
Rajkumar
23

La @WithMockUsercombinación de Spring Security y Hamcrest es containsStringuna solución simple y elegante:

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

Más ejemplos en github

Michael W
fuente
4

Aquí hay un ejemplo de cómo analizar la respuesta JSON e incluso cómo enviar una solicitud con un bean en forma JSON:

  @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());
  }

Como puede ver aquí, Bookhay un DTO de solicitud y UpdateBookResponseun objeto de respuesta analizado desde JSON. Es posible que desee cambiar la ObjectMapperconfiguración de Jakson .

Sergey Ponomarev
fuente
2
String body = mockMvc.perform(bla... bla).andReturn().getResolvedException().getMessage()

Esto debería darle el cuerpo de la respuesta. "Nombre de usuario ya tomado" en su caso.

justAnotherGuy
fuente
¿Dónde está la explicación? es necesario o puede dar un comentario en este tipo de respuesta
user1140237
2

aquí una manera más elegante

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

Puede usar el método 'getContentAsString' para obtener los datos de respuesta como una cadena.

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

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

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

Puede consultar este enlace para la aplicación de prueba.

Hari Krishna
fuente
1

Un posible enfoque es simplemente incluir la gsondependencia:

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

y analiza el valor para hacer tus verificaciones:

@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();
        HelloController.ResponseDto responseDto
                = new Gson().fromJson(responseBody, HelloController.ResponseDto.class);
        Assertions.assertThat(responseDto.message).isEqualTo("hello world!");
    }
}
Koray Tugay
fuente