Comment vérifier la chaîne dans le corps de la réponse avec mockMvc

243

J'ai un test d'intégration 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(?);
}

Dans la dernière ligne, je veux comparer la chaîne reçue dans le corps de la réponse à la chaîne attendue

Et en réponse, je reçois:

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

J'ai essayé quelques astuces avec content (), body () mais rien n'a fonctionné.

pbaranski
la source
19
Tout comme un conseil, le code d'état 400 ne doit pas être retourné pour quelque chose comme "Username already taken". Cela devrait être davantage un conflit 409.
Sotirios Delimanolis
Thanx - le but de ce test est de spécifier de telles choses.
pbaranski

Réponses:

356

Vous pouvez appeler andReturn()et utiliser l' MvcResultobjet renvoyé pour obtenir le contenu en tant que String.

Voir ci-dessous:

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
la source
7
@ TimBüthe Pouvez-vous clarifier? A @RestControllerindique que toutes les méthodes de gestionnaire sont implicitement annotées avec @ResponseBody. Cela signifie que Spring utilisera un HttpMessageConverterpour sérialiser la valeur de retour du gestionnaire et l'écrire dans la réponse. Vous pouvez très bien obtenir le corps content().
Sotirios Delimanolis
5
@SotiriosDelimanolis est correct ... Je regarde en ce moment le JSON retourné par getContentAsString()celui qui vient de mon @RestControllercontrôleur annoté.
Paul
J'ai trouvé ce que je cherchais dans le message d'erreur:result.getResponse().getErrorMessage()
whistling_marmot
andReturn () renvoie une valeur nulle
Giriraj
@Giriraj andReturnrenvoie un MvcResult, comme spécifié dans le javadoc ici .
Sotirios Delimanolis
105

La réponse de @Sotirios Delimanolis fait le travail mais je cherchais à comparer les chaînes dans cette assertion mockMvc

Alors voilà

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

Bien sûr, mon affirmation échoue:

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

car:

  MockHttpServletResponse:
            Body = "Something gone wrong"

Voilà donc la preuve que ça marche!

pbaranski
la source
17
Juste au cas où quelqu'un aurait des messages avec des ID dynamiques, comme je l'ai fait, il est utile de savoir que la méthode string () accepte également un hamcrest containsString matcher:.andExpect(content().string(containsString("\"Username already taken");
molholm
4
@ TimBüthe, c'est incorrect. Si vous avez un tel problème, vous devez le poster sous forme de question car ce n'est certainement pas le comportement attendu ni le comportement dont j'ai été témoin dans mon propre code.
Paul
2
Notez juste que l'importation est org.hamcrest.Matchers.containsString().
membersound
J'ai également utilisé org.hamcrest.Matchers.equalToIgnoringWhiteSpace()matcher pour ignorer tous les caractères d'espacement. Peut-être que ce sera un conseil utile pour quelqu'un
Iwo Kucharski
66

Spring MockMvc prend désormais en charge directement JSON. Vous dites donc simplement:

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

et contrairement à la comparaison de chaînes, il dira quelque chose comme "champ manquant xyz" ou "message Attendu" ok "a" nok ".

Cette méthode a été introduite au printemps 4.1.

vertti
la source
2
pourriez-vous donner un exemple complet? Vous n'avez ContentRequestMatcherspas besoin de prendre également en charge cette fonctionnalité?
Zarathustra
49

En lisant ces réponses, je vois beaucoup de choses concernant Spring version 4.x, j'utilise la version 3.2.0 pour diverses raisons. Des choses comme le support json directement depuis le content()n'est pas possible.

J'ai trouvé que l'utilisation MockMvcResultMatchers.jsonPathest vraiment facile et fonctionne un régal. Voici un exemple de test d'une méthode de publication.

Le bonus de cette solution est que vous êtes toujours en correspondance avec des attributs, sans compter sur des comparaisons de chaînes json complètes.

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

Le corps de la requête n'était qu'une chaîne json, que vous pouvez facilement charger à partir d'un vrai fichier de données fictives json si vous le vouliez, mais je ne l'ai pas inclus ici car cela aurait dévié de la question.

Le json retourné aurait ressemblé à ceci:

{
    "data":"some value"
}
Jeremy
la source
bravo pour ".andExpect (MockMvcResultMatchers.jsonPath (" $. data "). value (attenduData))"
attenduData
28

Extrait du tutoriel du printemps

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 à partir de import static org.hamcrest.Matchers.*;

jsonPath est disponible à partir de import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

et la jsonPathréférence peut être trouvée ici

user2829759
la source
1
Je reçois error: incompatible types: RequestMatcher cannot be converted to ResultMatcher pour.andExpect(content().contentType(contentType))
Ian Vaughan
@IanVaughan MockMvcResultMatchers.content (). ContentType (contentType)
Rajkumar
23

@WithMockUserLe containsStringmatcher de Spring Security et Hamcrest constitue une solution simple et élégante:

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

Plus d'exemples sur github

Michael W
la source
4

Voici un exemple comment analyser la réponse JSON et même comment envoyer une demande avec un bean sous forme 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());
  }

Comme vous pouvez le voir ici, il Books'agit d'un DTO de demande et d' UpdateBookResponseun objet de réponse analysé à partir de JSON. Vous souhaiterez peut-être modifier la ObjectMapperconfiguration de Jakson .

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

Cela devrait vous donner le corps de la réponse. "Nom d'utilisateur déjà pris" dans votre cas.

justAnotherGuy
la source
où est l'explication? il est nécessaire ou vous pouvez le donner en commentaire ce type de réponse
user1140237
2

ici une manière plus élégante

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

Vous pouvez utiliser la méthode «getContentAsString» pour obtenir les données de réponse sous forme de chaîne.

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

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

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

Vous pouvez renvoyer ce lien pour l'application de test.

Hari Krishna
la source
1

Une approche possible consiste à simplement inclure la gsondépendance:

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

et analyser la valeur pour effectuer vos vérifications:

@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
la source