So überprüfen Sie den String im Antworttext mit mockMvc

243

Ich habe einen einfachen Integrationstest

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

In der letzten Zeile möchte ich die im Antworttext empfangene Zeichenfolge mit der erwarteten Zeichenfolge vergleichen

Und als Antwort bekomme ich:

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

Versuchte einige Tricks mit content (), body (), aber nichts funktionierte.

pbaranski
quelle
19
Nur als Ratschlag, 400 Statuscode sollte nicht für so etwas zurückgegeben werden "Username already taken". Das sollte eher ein 409-Konflikt sein.
Sotirios Delimanolis
Vielen Dank - das Ziel dieses Tests ist es, solche Dinge zu spezifizieren.
Pbaranski

Antworten:

356

Sie können andReturn()das zurückgegebene MvcResultObjekt aufrufen und verwenden , um den Inhalt als zu erhalten String.

Siehe unten:

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
quelle
7
@ TimBüthe Kannst du das klarstellen? A @RestControllergibt an, dass alle Handler-Methoden implizit mit Anmerkungen versehen sind @ResponseBody. Dies bedeutet, dass Spring a verwendet, HttpMessageConverterum den Rückgabewert des Handlers zu serialisieren und in die Antwort zu schreiben. Sie können sehr viel den Körper mit bekommen content().
Sotirios Delimanolis
5
@SotiriosDelimanolis ist korrekt ... Ich schaue gerade auf den JSON getContentAsString(), der von meinem @RestControllerannotierten Controller zurückgegeben wurde.
Paul
Ich fand , was ich in der Fehlermeldung suchen:result.getResponse().getErrorMessage()
whistling_marmot
andReturn () gibt den Nullwert zurück
Giriraj
@Giriraj gibt andReturna zurück MvcResult, wie im Javadoc hier angegeben .
Sotirios Delimanolis
105

@Sotirios Delimanolis Antwort erledigt den Job, aber ich suchte nach einem Vergleich von Zeichenfolgen innerhalb dieser mockMvc-Behauptung

Hier ist es also

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

Natürlich scheitert meine Behauptung:

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

weil:

  MockHttpServletResponse:
            Body = "Something gone wrong"

Das ist also ein Beweis dafür, dass es funktioniert!

pbaranski
quelle
17
Nur für den Fall, dass jemand Nachrichten mit dynamischen IDs hat, wie ich es getan habe, ist es hilfreich zu wissen, dass die string () -Methode auch einen Hamcrest enthält , der String- Matcher enthält :.andExpect(content().string(containsString("\"Username already taken");
Molholm
4
@ TimBüthe, das ist falsch. Wenn Sie ein solches Problem haben, sollten Sie es als Frage posten, da dies definitiv nicht das erwartete Verhalten ist und auch nicht das Verhalten, das ich in meinem eigenen Code beobachtet habe.
Paul
2
Beachten Sie nur, dass der Import ist org.hamcrest.Matchers.containsString().
Membersound
Ich habe org.hamcrest.Matchers.equalToIgnoringWhiteSpace()Matcher auch verwendet , um alle Leerzeichen zu ignorieren. Vielleicht ist es ein nützlicher Tipp für jemanden
Iwo Kucharski
66

Spring MockMvc unterstützt jetzt direkt JSON. Also sagst du einfach:

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

und im Gegensatz zum String-Vergleich wird so etwas wie "fehlendes Feld xyz" oder "Nachricht erwartet" angezeigt.

Diese Methode wurde im Frühjahr 4.1 eingeführt.

vertti
quelle
2
Könnten Sie ein vollständiges Beispiel geben? Müssen Sie ContentRequestMatchersdiese Funktion nicht ebenfalls unterstützen?
Zarathustra
49

Wenn ich diese Antworten lese, sehe ich viel in Bezug auf Spring Version 4.x. Ich verwende Version 3.2.0 aus verschiedenen Gründen. Dinge wie json support direkt von der content()sind also nicht möglich.

Ich fand, dass die Verwendung MockMvcResultMatchers.jsonPathwirklich einfach ist und ein Vergnügen ist. Hier ist ein Beispiel zum Testen einer Post-Methode.

Der Vorteil dieser Lösung besteht darin, dass Sie immer noch mit Attributen übereinstimmen und sich nicht auf vollständige Vergleiche mit JSON-Zeichenfolgen verlassen.

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

Der Anfragetext war nur eine JSON-Zeichenfolge, die Sie leicht aus einer echten JSON-Mock-Datendatei laden können, wenn Sie möchten, aber ich habe das hier nicht aufgenommen, da es von der Frage abgewichen wäre.

Der tatsächlich zurückgegebene JSON hätte folgendermaßen ausgesehen:

{
    "data":"some value"
}
Jeremy
quelle
Ein großes Lob für ".andExpect (MockMvcResultMatchers.jsonPath (" $. data "). value (expectedData))"
user1697575
28

Entnommen aus dem Tutorial des Frühlings

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 ist erhältlich bei import static org.hamcrest.Matchers.*;

jsonPath ist erhältlich bei import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

und jsonPathReferenz finden Sie hier

user2829759
quelle
1
Ich bekomme error: incompatible types: RequestMatcher cannot be converted to ResultMatcher für.andExpect(content().contentType(contentType))
Ian Vaughan
@IanVaughan MockMvcResultMatchers.content (). ContentType (contentType)
Rajkumar
23

@WithMockUserDer containsStringMatcher von Spring Security und Hamcrest bietet eine einfache und elegante Lösung:

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

Weitere Beispiele zu Github

Michael W.
quelle
4

Hier ist ein Beispiel, wie Sie die JSON-Antwort analysieren und sogar eine Anforderung mit einer Bean in JSON-Form senden:

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

Wie Sie hier sehen können, Bookist dies ein Anforderungs-DTO und UpdateBookResponseein Antwortobjekt, das von JSON analysiert wurde. Möglicherweise möchten Sie die ObjectMapperKonfiguration des Jakson ändern .

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

Dies sollte Ihnen den Hauptteil der Antwort geben. "Benutzername bereits vergeben" in Ihrem Fall.

justAnotherGuy
quelle
Wo ist die Erklärung? es wird benötigt oder Sie können es in Kommentar dieser Art von Antwort geben
user1140237
2

hier eine elegantere Art und Weise

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

Sie können die Methode 'getContentAsString' verwenden, um die Antwortdaten als Zeichenfolge abzurufen.

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

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

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

Sie können diesen Link für die Testanwendung verwenden.

Hari Krishna
quelle
1

Ein möglicher Ansatz besteht darin, einfach die gsonAbhängigkeit einzuschließen:

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

und analysieren Sie den Wert, um Ihre Überprüfungen vorzunehmen:

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