372

I have simple integration test

@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 last line I want to compare string received in response body to expected string

And in response I get:

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

Tried some tricks with content(), body() but nothing worked.

Max
  • 915
  • 10
  • 28
pbaranski
  • 22,778
  • 19
  • 100
  • 117

15 Answers15

521

You can call andReturn() and use the returned MvcResult object to get the content as a String.

See below:

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
  • 274,122
  • 60
  • 696
  • 724
165

@Sotirios Delimanolis answer do the job however I was looking for comparing strings within this mockMvc assertion

So here it is

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

Of course my assertion fail:

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

because:

  MockHttpServletResponse:
            Body = "Something gone wrong"

So this is proof that it works!

pbaranski
  • 22,778
  • 19
  • 100
  • 117
  • 26
    Just in case someone has messages with dynamic IDs, like i did, it is helpfully to know that the string() method also accepts a hamcrest *containsString* matcher: `.andExpect(content().string(containsString("\"Username already taken");` – molholm May 26 '14 at 12:14
  • 4
    @TimBüthe, that is incorrect. If you've got such a problem you should post it as a question because that is definitely not the expected behavior nor is it behavior I've witnessed in my own code. – Paul Dec 10 '14 at 18:58
  • 2
    Just note that the import is `org.hamcrest.Matchers.containsString()`. – membersound Dec 16 '19 at 11:44
  • 1
    I also used `org.hamcrest.Matchers.equalToIgnoringWhiteSpace()` matcher to ignore all white-space characters. Maybe it will be useful tip for someone – Iwo Kucharski Apr 09 '20 at 12:43
  • To check for any object (not just a string), in my case it worked by extending the BaseMatcher class, as explained in the StackOverflow question here: https://stackoverflow.com/a/47719850/5538923 – marcor92 Aug 11 '22 at 10:48
88

Spring MockMvc now has direct support for JSON. So you just say:

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

and unlike string comparison, it will say something like "missing field xyz" or "message Expected 'ok' got 'nok'.

This method was introduced in Spring 4.1.

wired00
  • 13,930
  • 7
  • 70
  • 73
vertti
  • 7,539
  • 4
  • 51
  • 81
74

Reading these answers, I can see a lot relating to Spring version 4.x, I am using version 3.2.0 for various reasons. So things like json support straight from the content() is not possible.

I found that using MockMvcResultMatchers.jsonPath is really easy and works a treat. Here is an example testing a post method.

The bonus with this solution is that you're still matching on attributes, not relying on full json string comparisons.

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

The request body was just a json string, which you can easily load from a real json mock data file if you wanted, but I didnt include that here as it would have deviated from the question.

The actual json returned would have looked like this:

{
    "data":"some value"
}
Jeremy
  • 3,418
  • 2
  • 32
  • 42
  • 1
    kudos for ".andExpect(MockMvcResultMatchers.jsonPath("$.data").value(expectedData))" – user1697575 Sep 20 '19 at 19:59
  • If you statically import `MockMvcResultMatchers.jsonPath` and Hamcrest `Matchers.*` then these tests read more clearly: `.andExpect(jsonPath("$.emailAddress", is(expectedEmailAddress)))` – E-Riz Mar 06 '23 at 19:14
47

Taken from spring's tutorial

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 is available from import static org.hamcrest.Matchers.*;

jsonPath is available from import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

and jsonPath reference can be found here

cokeman19
  • 2,405
  • 1
  • 25
  • 40
user2829759
  • 3,372
  • 2
  • 29
  • 53
31

Spring security's @WithMockUser and hamcrest's containsString matcher makes for a simple and elegant solution:

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

More examples on github

Michael W
  • 3,515
  • 8
  • 39
  • 62
12

here a more elegant way

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

One possible approach is to simply include gson dependency:

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

and parse the value to make your verifications:

@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();
        ResponseDto responseDto
                = new Gson().fromJson(responseBody, ResponseDto.class);
        Assertions.assertThat(responseDto.message).isEqualTo("hello world!");
    }
}
Koray Tugay
  • 22,894
  • 45
  • 188
  • 319
9

Here is an example how to parse JSON response and even how to send a request with a bean in JSON form:

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

As you can see here the Book is a request DTO and the UpdateBookResponse is a response object parsed from JSON. You may want to change the Jackson's ObjectMapper configuration.

Koray Tugay
  • 22,894
  • 45
  • 188
  • 319
Sergey Ponomarev
  • 2,947
  • 1
  • 33
  • 43
6

Another option is:

when:

def response = mockMvc.perform(
            get('/path/to/api')
            .header("Content-Type", "application/json"))

then:

response.andExpect(status().isOk())
response.andReturn().getResponse().getContentAsString() == "what you expect"
4

Another example is:

.andExpect(jsonPath("$").value(containsString("You have successfully deleted")));

The body response:

Body = You have successfully deleted a [Object] with ID: 1

NXT Dev
  • 71
  • 4
3

You can use getContentAsString method to get the response data as string.

    String payload = "....";
    String apiToTest = "....";
    
    MvcResult mvcResult = mockMvc.
                perform(post(apiToTest).
                content(payload).
                contentType(MediaType.APPLICATION_JSON)).
                andReturn();
    
    String responseData = mvcResult.getResponse().getContentAsString();

You can refer this link for test application.

Koray Tugay
  • 22,894
  • 45
  • 188
  • 319
Hari Krishna
  • 3,658
  • 1
  • 36
  • 57
2
String body = mockMvc.perform(bla... bla).andReturn().getResolvedException().getMessage()

This should give you the body of the response. "Username already taken" in your case.

justAnotherGuy
  • 367
  • 4
  • 10
1

Here is a more production ready way of doing it where you if you may have big json responses then you do not have to clutter your test files with json strings, just load them from static Resources folder and assert them directly.

  @Test
  @DisplayName("invalid fields")
  void invalidfields() throws Exception {

    String request = getResourceFileAsString("test-data/http-request/invalid-fields.json");
    String response_file_path = "test-data/http-response/error-messages/invalid-fields.json";
    String expected_response = getResourceFileAsString(response_file_path);

    mockMvc.perform(evaluateRulesOnData(TRACKING_ID.toString(), request))
        .andExpect(status().isBadRequest())
        .andExpect(content().json(expected_response));
  }

helper function to load test files from classpath

  public static String getResourceFileAsString(String fileName) throws IOException {
    Resource resource = new ClassPathResource(fileName);
    File file = resource.getFile();
    return new String(Files.readAllBytes(file.toPath()));
  }

The expected response has an array with many elements in the list which are matched despite being in random order during each test run.

neshant sharma
  • 174
  • 2
  • 4
0

If your response is JSON and it is not so small you could do like following.

Create utility class:

@UtilityClass
public class FileUtils {
    public String readFromFile(String fileName) throws IOException {
        String filePath = String.format("responses/%s", fileName);
        File resource = new ClassPathResource(filePath).getFile();
        byte[] byteArray = Files.readAllBytes(resource.toPath());
        return new String(byteArray);
    }
}

And use it directly from your tests for example:

@Test
void getAllDepartments() throws Exception {
    mockMvc.perform(get("/v1/departments")
                    .header(AUTHORIZATION_HEADER, generateToken())
                    .contentType(MediaType.APPLICATION_JSON_VALUE))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().json(FileUtils.readFromFile("departments-response.json")));
}

where departments-response.json is:

[
  {
    "id": 1,
    "name": "Gryffindor"
  },
  {
    "id": 2,
    "name": "Hufflepuff"
  }
]
catch23
  • 17,519
  • 42
  • 144
  • 217