Jak sprawdzić ciąg znaków w treści odpowiedzi za pomocą mockMvc

243

Mam prosty test integracyjny

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

W ostatnim wierszu chcę porównać ciąg otrzymany w treści odpowiedzi z oczekiwanym ciągiem

W odpowiedzi otrzymuję:

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

Próbowałem kilku sztuczek z content (), body (), ale nic nie działało.

pbarański
źródło
19
Tak jak rada, kod statusu 400 nie powinien być zwracany za coś takiego "Username already taken". To powinno być bardziej konfliktem 409.
Sotirios Delimanolis,
Dzięki - celem tego testu jest określenie takich rzeczy.
pbarański

Odpowiedzi:

356

Możesz wywołać andReturn()i użyć zwróconego MvcResultobiektu, aby uzyskać zawartość jako String.

Patrz poniżej:

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
źródło
7
@ TimBüthe Czy możesz to wyjaśnić? A @RestControllerwskazuje, że wszystkie metody obsługi są domyślnie opatrzone adnotacjami @ResponseBody. Oznacza to, że Spring użyje a HttpMessageConverterdo serializacji wartości zwracanej przez moduł obsługi i zapisania go w odpowiedzi. Możesz bardzo mocno zdobyć ciało content().
Sotirios Delimanolis
5
@SotiriosDelimanolis ma rację ... Patrzę teraz na JSON zwrócony przez ten, getContentAsString()który pochodzi od mojego @RestControllerkontrolera z adnotacjami.
Paul
Znalazłem to, czego szukałem w komunikacie o błędzie:result.getResponse().getErrorMessage()
whistling_marmot
andReturn () zwraca wartość null
Giriraj
@Giriraj andReturnzwraca a MvcResult, jak określono w javadoc tutaj .
Sotirios Delimanolis
105

@Sotirios Delimanolis odpowiedź wykonuje pracę, ale szukałem porównywania ciągów w ramach tego twierdzenia mockMvc

Więc oto jest

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

Oczywiście moje twierdzenie się nie powiedzie:

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

ponieważ:

  MockHttpServletResponse:
            Body = "Something gone wrong"

To dowód na to, że działa!

pbarański
źródło
17
Na wypadek, gdyby ktoś miał wiadomości z dynamicznymi identyfikatorami, tak jak ja, dobrze jest wiedzieć, że metoda string () akceptuje również hamcrest zawiera Pasujący element dopasowania:.andExpect(content().string(containsString("\"Username already taken");
molholm
4
@ TimBüthe, to jest niepoprawne. Jeśli masz taki problem, powinieneś go zadać jako pytanie, ponieważ zdecydowanie nie jest to oczekiwane zachowanie, ani nie jest to zachowanie, którego byłem świadkiem w moim własnym kodzie.
Paul
2
Pamiętaj tylko, że import jest org.hamcrest.Matchers.containsString().
członkowieound
Użyłem również org.hamcrest.Matchers.equalToIgnoringWhiteSpace()Matchera do zignorowania wszystkich białych znaków. Może będzie to przydatna wskazówka dla kogoś
Iwo Kucharski
66

Spring MockMvc ma teraz bezpośrednie wsparcie dla JSON. Więc po prostu mówisz:

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

i w przeciwieństwie do porównania ciągów, powie coś w rodzaju „brak pola xyz” lub „komunikat Oczekiwany„ ok ”dostał„ nok ”.

Metodę tę wprowadzono wiosną 4.1.

vertti
źródło
2
czy możesz podać pełny przykład? Czy nie musisz ContentRequestMatchersteż obsługiwać tej funkcji?
Zarathustra,
49

Czytając te odpowiedzi, widzę wiele związanych z wersją Spring 4.x, używam wersji 3.2.0 z różnych powodów. Tak więc rzeczy takie jak wsparcie JSON od samego początku content()nie jest możliwe.

Odkryłem, że używanie MockMvcResultMatchers.jsonPathjest naprawdę łatwe i działa na ucztę. Oto przykład testowania metody postu.

Zaletą tego rozwiązania jest to, że nadal dopasowujesz atrybuty, a nie polegasz na pełnych porównaniach ciągów JSON.

(Za pomocą 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));

Treść żądania była tylko ciągiem json, który można łatwo załadować z prawdziwego fałszywego pliku danych json, jeśli chcesz, ale nie uwzględniłem go tutaj, ponieważ odbiegałoby to od pytania.

Rzeczywisty zwrócony Json wyglądałby tak:

{
    "data":"some value"
}
Jeremy
źródło
Wyrazy uznania dla „.andExpect (MockMvcResultMatchers.jsonPath („ $. data ”). wartość (oczekiwanych danych))”
user1697575 20.09.19
28

Zaczerpnięte z samouczka wiosny

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 jest dostępny od import static org.hamcrest.Matchers.*;

jsonPath jest dostępny od import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

i jsonPathreferencje można znaleźć tutaj

użytkownik 2829759
źródło
1
Mam error: incompatible types: RequestMatcher cannot be converted to ResultMatcher dla.andExpect(content().contentType(contentType))
Ian Vaughan
@IanVaughan MockMvcResultMatchers.content (). ContentType (contentType)
Rajkumar
23

Wiosenna ochrona @WithMockUseri containsStringdopasowanie hamcresta tworzą proste i eleganckie rozwiązanie:

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

Więcej przykładów na github

Michael W.
źródło
4

Oto przykład, jak parsować odpowiedź JSON, a nawet jak wysłać zapytanie z komponentem bean w formie 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());
  }

Jak widać tutaj, Bookjest to żądanie DTO i UpdateBookResponseobiekt odpowiedzi przeanalizowany z JSON. Możesz zmienić ObjectMapperkonfigurację Jaksona .

Siergiej Ponomariew
źródło
2
String body = mockMvc.perform(bla... bla).andReturn().getResolvedException().getMessage()

To powinno dać ci odpowiedź. „Nazwa użytkownika jest już zajęta” w twoim przypadku.

justAnotherGuy
źródło
gdzie jest wyjaśnienie? jest to konieczne lub możesz dać w komentarzu tego typu odpowiedź
1140237
2

tutaj bardziej elegancki sposób

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

Możesz użyć metody „getContentAsString”, aby uzyskać dane odpowiedzi w postaci ciągu.

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

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

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

Możesz skorzystać z tego linku do aplikacji testowej.

Hari Krishna
źródło
1

Jednym z możliwych podejść jest po prostu uwzględnienie gsonzależności:

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

i przeanalizuj wartość, aby dokonać weryfikacji:

@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
źródło