【问题标题】:MocMVC giving HttpMessageNotReadableExceptionMockMVC 给出 HttpMessageNotReadableException
【发布时间】:2019-12-05 00:36:13
【问题描述】:

我仍在学习如何进行测试,并且正在尝试让 MockMvc 测试为我工作。这是一个简单的 REST 控制器,此时仅使用帖子中的 json 信息进行一些身份验证。我实际上已经实现了代码,所以我知道它正在工作,因为我得到了正确输入的正确响应和我放在一起的错误消息,两者都是 json 格式。我的问题是测试一直失败并出现 HttpMessageNotReadableException,即使实际代码有效,所以我假设我的测试设置不正确。你们能提供的任何帮助都会很棒。

这是我的控制器

@Controller
public class RequestPaymentController {
protected final Log logger = LogFactory.getLog(getClass());
private PaymentService paymentService;
private LoginService loginService;

@Autowired
public void setPaymentService(PaymentService paymentService){
    this.paymentService =  paymentService;
}
@Autowired
public void setLoginService(LoginService loginService){
    this.loginService =  loginService;
}

@RequestMapping(value = "/requestpayment", method = RequestMethod.POST, headers="Accept=application/json")
@ResponseBody
public ResponseEntity<PaymentResult> handleRequestPayment(@RequestBody PaymentRequest paymentRequest, HttpServletRequest request, HttpServletResponse response, BindingResult result) throws Exception{
    ResponseEntity<PaymentResult> responseEntity = null;
    new LoginValidator().validate(paymentRequest, result);
    boolean valid = loginService.isLoginValid(paymentRequest, result);
    if (valid){
      responseEntity = setValidResponse(paymentRequest);
    }else {
        throw new TumsException("exception message");

    }
    return responseEntity;
}


private ResponseEntity<PaymentResult> setValidResponse(PaymentRequest paymentRequest){
    PaymentResult paymentResult = paymentService.getResults(paymentRequest);

    return new ResponseEntity<PaymentResult>(paymentResult, HttpStatus.OK);
}


}

这是我的测试代码:

public class RequestPaymentControllerTest {

PaymentService mockPaymentService;
RequestPaymentController requestPaymentController;
HttpServletRequest mockHttpServletRequest;
HttpServletResponse mockHttpServletResponse;
PaymentRequest mockPaymentRequest;
BindingResult mockBindingResult;
LoginService mockLoginService;
PaymentResult mockPaymentResult;
MockMvc mockMvc;


@Before
public void setUp() throws Exception {
    mockPaymentService = createMock(PaymentService.class);
    mockHttpServletRequest = createMock(HttpServletRequest.class);
    mockHttpServletResponse = createMock(HttpServletResponse.class);
    mockPaymentRequest = createMock(PaymentRequest.class);
    requestPaymentController = new RequestPaymentController();
    mockBindingResult = createMock(BindingResult.class);
    mockLoginService = createMock(LoginService.class);
    requestPaymentController.setPaymentService(mockPaymentService);
    mockPaymentResult = createMock(PaymentResult.class);
    mockMvc = MockMvcBuilders.standaloneSetup(new RequestPaymentController()).build();

}

@After
public void tearDown() throws Exception {
    mockPaymentService = null;
    mockHttpServletRequest = null;
    mockHttpServletResponse = null;
    mockPaymentRequest = null;
    requestPaymentController = null;
    mockBindingResult = null;
    mockLoginService = null;
    mockPaymentResult = null;
    mockMvc = null;
}


@Test
public void testHandleRequestPayment() throws Exception{
    initializeStateForHandleRequestPayment();
    createExpectationsForHandleRequestPayment();
    replayAndVerifyExpectationsForHandleRequestPayment();

}



private void initializeStateForHandleRequestPayment(){

}

private void createExpectationsForHandleRequestPayment(){
    mockPaymentRequest.getServiceUsername();
    expectLastCall().andReturn("testuser");
    mockPaymentRequest.getServicePassword();
    expectLastCall().andReturn("password1!");
    mockLoginService.isLoginValid(mockPaymentRequest,mockBindingResult);
    expectLastCall().andReturn(true);
    mockPaymentService.getResults(mockPaymentRequest);
    expectLastCall().andReturn(mockPaymentResult);
}

private void replayAndVerifyExpectationsForHandleRequestPayment() throws Exception{
    replay(mockPaymentService, mockBindingResult, mockHttpServletRequest, mockHttpServletResponse, mockPaymentRequest, mockLoginService);
    requestPaymentController.setLoginService(mockLoginService);
    requestPaymentController.handleRequestPayment(mockPaymentRequest, mockHttpServletRequest, mockHttpServletResponse, mockBindingResult);
    mockMvc.perform(post("/requestpayment")
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON))
            .andDo(print())
            .andExpect(status().isBadRequest());
    verify(mockPaymentService, mockBindingResult, mockHttpServletRequest, mockHttpServletResponse, mockPaymentRequest, mockLoginService);

}
}

andDo(print())的结果是:

MockHttpServletRequest:
     HTTP Method = POST
     Request URI = /requestpayment
      Parameters = {}
         Headers = {Content-Type=[application/json], Accept=[application/json]}

         Handler:
            Type = portal.echecks.controller.RequestPaymentController
          Method = public org.springframework.http.ResponseEntity<portal.echecks.model.PaymentResult> portal.echecks.controller.RequestPaymentController.handleRequestPayment(portal.echecks.model.PaymentRequest,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,org.springframework.validation.BindingResult) throws java.lang.Exception

  Resolved Exception:
            Type = org.springframework.http.converter.HttpMessageNotReadableException

    ModelAndView:
       View name = null
            View = null
           Model = null

        FlashMap:

MockHttpServletResponse:
          Status = 400
   Error message = null
         Headers = {}
    Content type = null
            Body = 
   Forwarded URL = null
  Redirected URL = null
         Cookies = []

Process finished with exit code 0

如您所见,当我期待一个错误的请求状态时,测试通过了,但我已经输入了日志,并且我知道我发回的 ResponseBody 的状态为 200。就像我说的,这是我第一次使用 MockMvc,所以我认为我没有设置正确的东西。有什么建议么?

【问题讨论】:

    标签: spring unit-testing spring-mvc


    【解决方案1】:

    HttpMessageNotReadableException

    在读取方法时由HttpMessageConverter实现抛出 失败。

    您的回复中还会收到 400 Bad Request。这都应该告诉您,您没有发送服务器所期望的内容。您的服务器期望什么?

    @RequestMapping(value = "/requestpayment", method = RequestMethod.POST, headers="Accept=application/json")
    @ResponseBody
    public ResponseEntity<PaymentResult> handleRequestPayment(@RequestBody PaymentRequest paymentRequest, HttpServletRequest request, HttpServletResponse response, BindingResult result) throws Exception{
    

    这里主要是@RequestBody注解的参数。因此,您告诉您的服务器尝试从 HTTP POST 请求的正文中反序列化 PaymentRequest 实例。

    让我们看看你提出的要求

    mockMvc.perform(post("/requestpayment")
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON))
            .andDo(print())
            .andExpect(status().isBadRequest());
    

    我没有看到您为请求提供正文。那里应该有一个 content(String) 调用来设置 POST 请求的内容。此内容应该是 PaymentRequest 的 JSON 序列化。

    请注意,由于您使用的是StandaloneMockMvcBuilder,您可能需要自己设置HttpMessageConverter 实例,即。 MappingJackson2HttpMessageConverter 用于序列化和反序列化 JSON。


    请注意,BindingResult 参数应紧跟在与其相关的参数之后。像这样

    @RequestMapping(value = "/requestpayment", method = RequestMethod.POST, headers="Accept=application/json")
    @ResponseBody
    public ResponseEntity<PaymentResult> handleRequestPayment(@Valid @RequestBody PaymentRequest paymentRequest, BindingResult result, HttpServletRequest request, HttpServletResponse response) throws Exception{
    

    别忘了@Valid

    注意这个

    requestPaymentController.setLoginService(mockLoginService);
    requestPaymentController.handleRequestPayment(mockPaymentRequest, mockHttpServletRequest, mockHttpServletResponse, mockBindingResult);
    

    与您正在进行的MockMvc 测试完全无关。

    【讨论】:

    • 谢谢,Sotirios!这一切都是有道理的,现在我将尝试解决它。我很欣赏如此彻底的答复。我试图找到一个很好的例子来说明这个设置,但它们都与我正在做的事情有很大的不同,因此很难将它们整合在一起。
    • @coffeeNjava 官方文档很详细。见here
    • 原来如此。那将是我下午的阅读。再次感谢!
    【解决方案2】:

    在我的例子中,sprint mvc w/jackson (jackson-mapper-asl, v-1.9.10) 反序列化需要 JSON 解析器。而且jackson需要一个默认构造函数来进行http请求消息的反序列化,如果没有默认构造函数,jackson会出现反射问题并抛出HttpMessageNotReadableException异常。

    也就是说,所有用作请求体的类/子类,(在这种情况下)都需要一个默认构造函数。在我尝试添加自定义转换器和我在 stackoverflow 中得到的其他建议徒劳后,这让我付出了一些时间。

    或者您可以添加自定义反序列化器或 Mixin 注释以避免在任何地方分层添加默认构造函数。如此处所述:http://blogs.jbisht.com/blogs/2016/09/12/Deserialize-json-with-Java-parameterized-constructor。如果您有兴趣,请查看这里。


    这里似乎重复了 > Spring HttpMessageNotReadableException.

    【讨论】:

      【解决方案3】:

      确保以下几点:

      • 返回对象实现可序列化
      • @ResponseBody 控制器方法上使用的注解
      • 在你的单元测试中

        @ExtendWith(SpringExtension.class) @ContextConfiguration(类 = {....}) @WebMvcTest @AutoConfigureMockMvc

      【讨论】:

        【解决方案4】:

        回答可能为时已晚,但以防万一有人仍在查看此页面。

        正如@Sotirios Delimanolis 所提到的,问题是由于一个错误的请求 - 在参数中指定了一个“@RequestBody”,但从未在请求​​正文中提供。因此,如果您使用 'content(someRequestString)' 将其添加到请求中,如下所示,它应该可以工作。

        PaymentRequest  paymentRequest  = new PaymentRequest(...);
        String requestBody = new ObjectMapper().valueToTree(paymentRequest).toString();
        mockMvc.perform(post("/requestpayment")
                        .content(requestBody)
                        .contentType(MediaType.APPLICATION_JSON))
                        .andExpect(status().isOk())
                        .andExpect(jsonPath("$.status").value("SUCCESS"))
                        .andExpect(jsonPath("$.paymentAmount", is(20)));
        

        jsonPath 可用于验证响应的属性。在上面的示例中,假设 PaymentResponse 在 json 响应中具有 statuspaymentAmount 属性。这些部分很容易验证。

        您可能会遇到类似的错误 -

        NoClassDefFoundError: com/jayway/jsonpath/Predicate

        在使用 jsonPath 时。因此,请确保将其显式添加到类路径中,因为它是 spring-test 中的可选依赖项,并且不可传递。如果使用 maven,请执行以下操作:

        <dependency>
          <groupId>com.jayway.jsonpath</groupId>
          <artifactId>json-path</artifactId>
          <version>2.4.0</version>
          <scope>test</scope>      
        </dependency>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-11-01
          • 2021-02-12
          • 1970-01-01
          • 2018-03-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-12-07
          相关资源
          最近更新 更多