使用 Spring MockMVC 测试 Spring 的 @RequestBody

Posted

技术标签:

【中文标题】使用 Spring MockMVC 测试 Spring 的 @RequestBody【英文标题】:Testing Spring's @RequestBody using Spring MockMVC 【发布时间】:2013-12-28 13:20:43 【问题描述】:

我正在尝试使用 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 测试此方法?

【问题讨论】:

您必须发布实际课程。使用 400,Spring 无法将您的请求正文转换为 ObjectBean 对象。 感谢 Sotirios,这是我正在调查的日期格式问题。我正在传递一个 java.util 日期,而 gson 不喜欢它。 嗨,Sotirios。是的,不幸的是,这个问题仍然存在!我在这里问了一个关于日期格式的问题:***.com/questions/20509883/mysql-insert-gson-date ObjectBean 类中日期字段的类型是什么? java.util.Datejava.util.Calendar 还是 String?还是其他? 【参考方案1】:

使用这个

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

如 cmets 中所述,这是有效的,因为对象被转换为 json 并作为请求正文传递。此外,contentType 定义为 Json (APPLICATION_JSON_UTF8)。

More info on the HTTP request body structure

【讨论】:

@WendyG 因为对象被转换为json并作为请求体传递(另外,contentType定义为Json)。有关 HTTP 请求正文结构的更多信息:developer.mozilla.org/en-US/docs/Web/HTTP/Messages#Body @JonyD 我被告知之前没有在答案中注明评论作者,但很抱歉我弄错了你的名字。 为什么不直接使用 MediaType.APPLICATION_JSON? MediaType.APPLICATION_JSON_UTF8 也存在,但自 5.2 起已被弃用 这个修复对我有用:)【参考方案2】:

以下对我有用,

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

【讨论】:

【参考方案3】:

问题是您正在使用自定义 Gson 对象序列化您的 bean,而应用程序正在尝试使用 Jackson ObjectMapper(在 MappingJackson2HttpMessageConverter 内)反序列化您的 JSON。

如果你打开你的服务器日志,你应该会看到类似的东西

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"])

在其他堆栈跟踪中。

一种解决方案是将您的 Gson 日期格式设置为上述之一(在堆栈跟踪中)。

另一种方法是注册您自己的MappingJackson2HttpMessageConverter,方法是将您自己的ObjectMapper 配置为与您的Gson 具有相同的日期格式。

【讨论】:

感谢 Sotirios,这是一个非常有用的课程。我曾假设反序列化将采用我列出的格式:yyyy-mm-dd-hh:mm:ss。这是因为@RequestBody 使用杰克逊吗?此外,这回答了我上面链接的另一个问题...... @MattB 涉及 2 个不同的进程。您正在使用您指定的格式进行序列化。您正在使用自己的格式的单独应用程序中反序列化。【参考方案4】:

我在更新版本的 Spring 中遇到了类似的问题。我尝试使用new ObjectMapper().writeValueAsString(...),但它不适用于我的情况。

我实际上有一个 JSON 格式的 String,但我觉得它实际上是将每个字段的 toString() 方法转换为 JSON。在我的例子中,日期LocalDate 字段最终会是:

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

这不是发送请求的最佳日期格式...

最后,就我而言,最简单的解决方案是使用 Spring ObjectMapper。它的行为更好,因为它使用 Jackson 来构建具有复杂类型的 JSON。

@Autowired
private ObjectMapper objectMapper;

我只是在测试中使用它:

mockMvc.perform(post("/api/")
                .content(objectMapper.writeValueAsString(...))
                .contentType(MediaType.APPLICATION_JSON)
);

【讨论】:

以上是关于使用 Spring MockMVC 测试 Spring 的 @RequestBody的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot WebFlux 测试未找到 MockMvc

使用 spring-data-jpa 和 MockMvc 进行 spring boot junit 测试

在 Spring Boot with Slice 中使用 MockMVC 测试静态内容

在 Spring Boot 1.4 MVC 测试中使用 @WebMvcTest 设置 MockMvc

spring boot 测试无法注入 TestRestTemplate 和 MockMvc

使用 MockMvc 和 AutoConfigureMockMvc 测试 Spring Boot Web 应用程序时出现 LazyInitializationException 的原因是啥