Como verificar String no corpo da resposta com mockMvc

243

Eu tenho teste de integração simples

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

Na última linha, quero comparar a sequência recebida no corpo da resposta à sequência esperada

E em resposta eu recebo:

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

Tentei alguns truques com conteúdo (), corpo (), mas nada funcionou.

pbaranski
fonte
19
Assim como conselhos, o código de status 400 não deve ser retornado para algo como "Username already taken". Isso deveria ser mais um conflito.
Sotirios Delimanolis
Thanx - o objetivo deste teste é especificar essas coisas.
Pbaranski

Respostas:

357

Você pode chamar andReturn()e usar o MvcResultobjeto retornado para obter o conteúdo como um String.

Ver abaixo:

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
fonte
7
@ TimBüthe Você pode esclarecer? A @RestControllerindica que todos os métodos de manipulador estão implicitamente anotados @ResponseBody. Isso significa que o Spring usará a HttpMessageConverterpara serializar o valor de retorno do manipulador e gravá-lo na resposta. Você pode muito bem obter o corpo content().
Sotirios Delimanolis
5
@SotiriosDelimanolis está correto ... Estou olhando agora para o JSON retornado por getContentAsString()aquele que veio do meu @RestControllercontrolador anotado.
Paul
Encontrei o que estava procurando na mensagem de erro:result.getResponse().getErrorMessage()
whistling_marmot
andReturn () está retornando valor nulo
Giriraj 28/01
@Giriraj andReturnretorna a MvcResult, conforme especificado no javadoc aqui .
Sotirios Delimanolis
105

@Sotirios Delimanolis reply fazer o trabalho, no entanto, eu estava procurando comparar seqüências de caracteres dentro desta afirmação mockMvc

Então aqui está

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

É claro que minha afirmação falha:

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"

Portanto, isso é prova de que funciona!

pbaranski
fonte
17
No caso de alguém ter mensagens com IDs dinâmicos, como eu, é útil saber que o método string () também aceita um hamcrest containsString matcher:.andExpect(content().string(containsString("\"Username already taken");
Mølholm
4
@ TimBüthe, isso está incorreto. Se você tem um problema desse tipo, deve postá-lo como uma pergunta, porque esse definitivamente não é o comportamento esperado nem é o que eu testemunhei em meu próprio código.
Paul
2
Apenas observe que a importação é org.hamcrest.Matchers.containsString().
membersound
Também usei o org.hamcrest.Matchers.equalToIgnoringWhiteSpace()matcher para ignorar todos os caracteres de espaço em branco. Talvez seja uma dica útil para alguém
Iwo Kucharski
66

O Spring MockMvc agora tem suporte direto para JSON. Então você apenas diz:

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

e, diferentemente da comparação de cadeias, ele diz algo como "campo ausente xyz" ou "mensagem Esperado 'ok' obteve 'nok'.

Este método foi introduzido no Spring 4.1.

vertti
fonte
2
você poderia fornecer um exemplo completo? Não precisa ContentRequestMatcherssuportar esse recurso também?
Zaratustra
49

Lendo essas respostas, posso ver muita coisa relacionada à versão 4.x do Spring, estou usando a versão 3.2.0 por vários motivos. Portanto, coisas como o json support diretamente do content()não são possíveis.

Eu descobri que o uso MockMvcResultMatchers.jsonPathé realmente fácil e funciona. Aqui está um exemplo de teste de um método de postagem.

O bônus com esta solução é que você ainda está correspondendo aos atributos, não confiando nas comparações completas do json string.

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

O corpo da solicitação era apenas uma string json, que você pode carregar facilmente de um arquivo de dados simulado json real, se quisesse, mas eu não o incluí aqui, pois teria se desviado da pergunta.

O json real retornado teria a seguinte aparência:

{
    "data":"some value"
}
Jeremy
fonte
parabéns por ".andExpect (MockMvcResultMatchers.jsonPath (" $. data "). value (
pectedData
28

Retirado do tutorial da 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á disponível em import static org.hamcrest.Matchers.*;

jsonPath está disponível em import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

e jsonPathreferência pode ser encontrada aqui

user2829759
fonte
1
Eu recebo error: incompatible types: RequestMatcher cannot be converted to ResultMatcher para.andExpect(content().contentType(contentType))
Ian Vaughan
@IanVaughan MockMvcResultMatchers.content (). ContentType (contentType)
Rajkumar
23

@WithMockUserO containsStringmatcher de segurança de primavera e hamcrest cria uma solução simples e 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.")));
}

Mais exemplos no github

Michael W
fonte
4

Aqui está um exemplo de como analisar a resposta JSON e até como enviar uma solicitação com um bean no formato 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 você pode ver aqui, o BookDTO de solicitação UpdateBookResponseé e o objeto de resposta é analisado a partir do JSON. Você pode alterar a ObjectMapperconfiguração do Jakson .

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

Isso deve fornecer o corpo da resposta. "Nome de usuário já utilizado" no seu caso.

justAnotherGuy
fonte
Onde está a explicação? ele é necessário ou você pode dá-lo no comentário deste tipo de resposta
user1140237
2

aqui uma maneira mais elegante

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

Você pode usar o método 'getContentAsString' para obter os dados de resposta como sequência.

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

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

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

Você pode consultar este link para o aplicativo de teste.

Hari Krishna
fonte
1

Uma abordagem possível é simplesmente incluir gsondependência:

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

e analise o valor para fazer suas verificações:

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