使用 Spring MockMVC 测试 Spring 的@RequestBody

我正在尝试使用 Spring 的 MockMVC 框架来测试一种将对象发布到数据库的方法。我已经构建了如下的测试:

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

我正在测试的方法使用 Spring 的@RequestBody 接收 ObjectBean,但测试总是返回400错误。

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

Gson 在测试中创建的 json:

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

ObjectBean 类

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
}

所以我的问题是: 如何使用 Spring MockMVC 测试这个方法?

207075 次浏览

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.

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

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

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