105

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?

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
Matt
  • 5,408
  • 14
  • 52
  • 79
  • You will have to post the actual class. With a 400, Spring is failing to convert your request body to an `ObjectBean` object. – Sotirios Delimanolis Dec 10 '13 at 21:16
  • 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. – Matt Dec 10 '13 at 21:38
  • Hi Sotirios. Yeah, unfortunately this problem persists! I've asked a question concerning the date format here: http://stackoverflow.com/questions/20509883/mysql-insert-gson-date – Matt Dec 11 '13 at 03:19
  • What is the type of the date field in the `ObjectBean` class? `java.util.Date`, `java.util.Calendar`, or `String`? Or some other? – Sotirios Delimanolis Dec 11 '13 at 03:22

5 Answers5

161

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

JonyD
  • 1,237
  • 3
  • 21
  • 34
Priyanka Gupta
  • 1,634
  • 1
  • 10
  • 3
  • 1
    @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: https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages#Body – JonyD Nov 06 '18 at 16:28
  • 1
    @JonyD I have been told off for not attributing comment writers in the answer before, but sorry I got your name wrong. – WendyG Nov 07 '18 at 10:24
  • 3
    Why not just use MediaType.APPLICATION_JSON? MediaType.APPLICATION_JSON_UTF8 also exists but it's been deprecated since 5.2 – Igorski Feb 16 '20 at 08:48
  • This fix worked for me :) – Arun Sep 27 '21 at 10:51
49

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);
    }
}
Vikki
  • 1,897
  • 1
  • 17
  • 24
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.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • 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... – Matt Dec 11 '13 at 03:45
  • @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. – Sotirios Delimanolis Dec 11 '13 at 03:47
7

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)
);
Jojoes
  • 180
  • 2
  • 9
0

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.

Dave Kraczo
  • 748
  • 9
  • 9