Testing Spring's @RequestBody using Spring MockMVC
Asked Answered
E

5

111

I am trying to test a method that posts an object to the database using Spring's MockMVC framework. I've constructed the test as follows:

@Test
public void testInsertObject() throws Exception { 

    String url = BASE_URL + "/object";

    ObjectBean anObject = new ObjectBean();
    anObject.setObjectId("33");
    anObject.setUserId("4268321");
    //... more

    Gson gson = new Gson();
    String json = gson.toJson(anObject);

    MvcResult result = this.mockMvc.perform(
            post(url)
            .contentType(MediaType.APPLICATION_JSON)
            .content(json))
            .andExpect(status().isOk())
            .andReturn();
}

The method I'm testing uses Spring's @RequestBody to receive the ObjectBean, but the test always returns a 400 error.

@ResponseBody
@RequestMapping(    consumes="application/json",
                    produces="application/json",
                    method=RequestMethod.POST,
                    value="/object")
public ObjectResponse insertObject(@RequestBody ObjectBean bean){

    this.photonetService.insertObject(bean);

    ObjectResponse response = new ObjectResponse();
    response.setObject(bean);

    return response;
}

The json created by gson in the test:

{
   "objectId":"33",
   "userId":"4268321",
   //... many more
}

The ObjectBean class

public class ObjectBean {

private String objectId;
private String userId;
//... many more

public String getObjectId() {
    return objectId;
}

public void setObjectId(String objectId) {
    this.objectId = objectId;
}

public String getUserId() {
    return userId;
}

public void setUserId(String userId) {
    this.userId = userId;
}
//... many more
}

So my question is: how to I test this method using Spring MockMVC?

Exaggerative answered 10/12, 2013 at 20:20 Comment(4)
You will have to post the actual class. With a 400, Spring is failing to convert your request body to an ObjectBean object.Varietal
Thanks Sotirios, it's a date format issue I'm looking into. I'm passing a java.util Date and gson doesn't like it.Exaggerative
Hi Sotirios. Yeah, unfortunately this problem persists! I've asked a question concerning the date format here: https://mcmap.net/q/196180/-mysql-insert-gson-dateExaggerative
What is the type of the date field in the ObjectBean class? java.util.Date, java.util.Calendar, or String? Or some other?Varietal
V
167

Use this one

public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));

@Test
public void testInsertObject() throws Exception { 
    String url = BASE_URL + "/object";
    ObjectBean anObject = new ObjectBean();
    anObject.setObjectId("33");
    anObject.setUserId("4268321");
    //... more
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
    ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
    String requestJson=ow.writeValueAsString(anObject );

    mockMvc.perform(post(url).contentType(APPLICATION_JSON_UTF8)
        .content(requestJson))
        .andExpect(status().isOk());
}

As described in the comments, this works because the object is converted to json and passed as the request body. Additionally, the contentType is defined as Json (APPLICATION_JSON_UTF8).

More info on the HTTP request body structure

Variant answered 15/2, 2016 at 6:40 Comment(3)
@WendyG Because the object is converted to json and passed as the request body (additionally, the contentType is defined as Json). More info on the HTTP request body structure here: developer.mozilla.org/en-US/docs/Web/HTTP/Messages#BodyRoderica
@Roderica I have been told off for not attributing comment writers in the answer before, but sorry I got your name wrong.Bayly
Why not just use MediaType.APPLICATION_JSON? MediaType.APPLICATION_JSON_UTF8 also exists but it's been deprecated since 5.2Ezaria
R
52

the following works for me,

  mockMvc.perform(
            MockMvcRequestBuilders.post("/api/test/url")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(asJsonString(createItemForm)))
            .andExpect(status().isCreated());

  public static String asJsonString(final Object obj) {
    try {
        return new ObjectMapper().writeValueAsString(obj);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
Range answered 10/1, 2019 at 3:28 Comment(0)
V
9

The issue is that you are serializing your bean with a custom Gson object while the application is attempting to deserialize your JSON with a Jackson ObjectMapper (within MappingJackson2HttpMessageConverter).

If you open up your server logs, you should see something like

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not construct instance of java.util.Date from String value '2013-34-10-10:34:31': not a valid representation (error: Failed to parse Date value '2013-34-10-10:34:31': Can not parse date "2013-34-10-10:34:31": not compatible with any of standard forms ("yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "EEE, dd MMM yyyy HH:mm:ss zzz", "yyyy-MM-dd"))
 at [Source: java.io.StringReader@baea1ed; line: 1, column: 20] (through reference chain: com.spring.Bean["publicationDate"])

among other stack traces.

One solution is to set your Gson date format to one of the above (in the stacktrace).

The alternative is to register your own MappingJackson2HttpMessageConverter by configuring your own ObjectMapper to have the same date format as your Gson.

Varietal answered 11/12, 2013 at 3:35 Comment(2)
Thanks Sotirios, this is a very helpful lesson. I had assumed that the deserialization would just take the format that I listed: yyyy-mm-dd-hh:mm:ss. Is this because the @RequestBody uses Jackson? Also, this answers my other question that I linked above...Exaggerative
@MattB There are 2 different processes involved. You are serializing in on process with the format you specified. You are deserializing in a separate application that is using its own format.Varietal
A
9

I have encountered a similar problem with a more recent version of Spring. I tried to use a new ObjectMapper().writeValueAsString(...) but it would not work in my case.

I actually had a String in a JSON format, but I feel like it is literally transforming the toString() method of every field into JSON. In my case, a date LocalDate field would end up as:

"date":{"year":2021,"month":"JANUARY","monthValue":1,"dayOfMonth":1,"chronology":{"id":"ISO","calendarType":"iso8601"},"dayOfWeek":"FRIDAY","leapYear":false,"dayOfYear":1,"era":"CE"}

which is not the best date format to send in a request ...

In the end, the simplest solution in my case is to use the Spring ObjectMapper. Its behaviour is better since it uses Jackson to build your JSON with complex types.

@Autowired
private ObjectMapper objectMapper;

and I simply used it in my test:

mockMvc.perform(post("/api/")
                .content(objectMapper.writeValueAsString(...))
                .contentType(MediaType.APPLICATION_JSON)
);
Appassionato answered 26/2, 2021 at 13:1 Comment(1)
I have done this, but its not working at all in my case, I have seen it is formatting th e class properly, Its just that it is printing bad requestStingaree
L
2

You can also get easly json content from file, it is helpful when content is big.

You can create common package and add static method

public static String getRequestBodyFromFile(String fileLocation) throws IOException {
        File file = ResourceUtils.getFile(String.format("classpath:%s", fileLocation));
        return new String(Files.readAllBytes(file.toPath()));
    }

and then just add file to your test resources and load at test

mockMvc.perform(post("/yourpath")
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(getRequestBodyFromFile("resourcespath/yourjsonfile.json")))
                .andExpect(status()
                        .isOk())

hope will be helpful for someone.

Lanettelaney answered 19/6, 2023 at 6:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.