How to extract value from JSON response when using Spring MockMVC

17,815

Solution 1

I have managed to solve my problem using Spring MockMVC result handler. I created a testing utility to convert the JSON string back to an object and so allowing me to get the ID.

Conversion Utility:

 public static <T>  Object convertJSONStringToObject(String json, Class<T> objectClass) throws IOException {
    ObjectMapper mapper = new ObjectMapper();
    mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

    JavaTimeModule module = new JavaTimeModule();
    mapper.registerModule(module);
    return mapper.readValue(json, objectClass);
}

Unit Test:

 @Test
@Transactional
public void createNewWorkWorkWhenCreatedJobItemAndQuantitiesPoolShouldBeCreated() throws Exception {

    mockMvc.perform(post("/api/tracker/jobs/work")
        .contentType(TestUtil.APPLICATION_JSON_UTF8)
        .content(TestUtil.convertObjectToJsonBytes(workRequest)))
        .andExpect(status().isCreated())
        .andDo(mvcResult -> {
            String json = mvcResult.getResponse().getContentAsString();
            workRequestResponse = (WorkRequestResponse) TestUtil.convertJSONStringToObject(json, WorkRequestResponse.class);
        });

    Work work = workService.findWorkById(workRequestResponse.getWorkId());

    assertThat(work.getJobItem().getJobItemName()).isEqualTo(workRequest.getJobItem().getJobItemName());
    assertThat(work.getJobItem().getQuantities()).hasSize(workRequest.getQuantities().size());
    assertThat(work.getJobItem().getQuantityPools()).hasSize(workRequest.getQuantities().size());
}

Solution 2

You can simply use JsonPath.read on the result object:

MvcResult result = mockMvc.perform(post("/api/tracker/jobs/work")
    .contentType(TestUtil.APPLICATION_JSON_UTF8)
    .content(TestUtil.convertObjectToJsonBytes(workRequest)))
    .andExpect(status().isCreated())
    .andReturn();    

String id = JsonPath.read(result.getResponse().getContentAsString(), "$.id")

Work work = workService.findWorkById(id);

...

Solution 3

One way to retrieve an arbitrary, generic value from the JSON response is to leverage the jsonPath() matcher from the MockMVC library and couple it with a custom matcher that captures all values it is asked to match.

First, the custom matcher:

import org.hamcrest.BaseMatcher;

/**
 * Matcher which always returns true, and at the same time, captures the
 * actual values passed in for matching. These can later be retrieved with a
 * call to {@link #getLastMatched()} or {@link #getAllMatched()}.
 */
public static class CapturingMatcher extends BaseMatcher<Object> {

    private List<Object> matchedList = new ArrayList<>();

    @Override
    public boolean matches(Object matched) {
        matchedList.add(matched);
        return true;
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("any object");
    }

    /**
     * The last value matched.
     */
    public Object getLastMatched() {
        return matchedList.get(matchedList.size() - 1);
    }

    /**
     * All the values matched, in the order they were requested for
     * matching.
     */
    public List<Object> getAllMatched() {
        return Collections.unmodifiableList(matchedList);
    }
}

Now, use the custom matcher to capture values and use the jsonPath() matcher to identify what should be captured:

@Test
@WithMockUser(username = "reviewer", authorities = {ROLE_USER})
public void testGetRemediationAction() throws Exception {

    CapturingMatcher capturingMatcher = new CapturingMatcher();

    // First request a list of all the available actions
    mvc.perform(get("/api/remediation/action").accept(VERSION_1_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.content[*].remediations[*].id", hasSize(12)))
            .andExpect(jsonPath("$.content[*].remediations[*].id", capturingMatcher));

    // Grab an ID from one of the available actions
    Object idArray = capturingMatcher.getLastMatched();
    assertThat(idArray).isInstanceOf(JSONArray.class);
    JSONArray jsonIdArray = (JSONArray) idArray;
    String randomId = (String) jsonIdArray.get(new Random().nextInt(12));

    // Now retrieve the chosen action
    mvc.perform(get("/api/remediation/action/" + randomId).accept(VERSION_1_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id", is(randomId)));
}
Share:
17,815
Chol Nhial
Author by

Chol Nhial

Updated on June 07, 2022

Comments

  • Chol Nhial
    Chol Nhial almost 2 years

    I have an endpoint that accepts a POST request. I want to get the ID of the newly created entity from the JSON response.

    Below is a segment of my code where I'm attempting to do that.

    mockMvc.perform(post("/api/tracker/jobs/work")
            .contentType(TestUtil.APPLICATION_JSON_UTF8)
            .content(TestUtil.convertObjectToJsonBytes(workRequest)))
            .andExpect(status().isCreated());
    

    If I get that ID I'll query the database for the newly created entity and do some assertions like below:

    Work work = work service.findWorkById(id);
    
    assertThat(work.getJobItem().getJobItemName()).isEqualTo(workRequest.getJobItem().getJobItemName());
    assertThat(work.getJobItem().getQuantities()).hasSize(workRequest.getQuantities().size());
    assertThat(work.getJobItem().getQuantityPools()).hasSize(workRequest.getQuantities().size());
    
  • Skizo-ozᴉʞS
    Skizo-ozᴉʞS about 5 years
    Is there another way instead using JavaTimeModule class?
  • Shawn. L
    Shawn. L almost 3 years
    This works just perfectly in my case. Thank you for mentioning JsonPath.