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ó.


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 

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!


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.


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

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í


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


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 .


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.


¿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")));

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.


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!");
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.