MocMVC给出了HttpMessageNotReadableException

时间:2014-02-08 16:56:31

标签: spring unit-testing spring-mvc

我还在学习测试方法,我正在尝试让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,所以我认为我没有正确设置。有什么建议?

4 个答案:

答案 0 :(得分:13)

HttpMessageNotReadableException

  

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

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

@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测试完全无关。

答案 1 :(得分:0)

就我而言,作为带有sackson的sprint mvc(jackson-mapper-asl,v-1.9.10),反序列化需要JSON解析器。杰克逊需要为HTTP请求消息反序列化使用默认的构造函数,如果没有默认的构造函数,杰克逊将遇到反射问题并抛出HttpMessageNotReadableException异常。

这就是说,用作请求正文的所有类/子类(在这种情况下)都需要默认的构造函数。在尝试添加自定义转换器和其他徒劳的stackoverflow建议之后,这花了我几分钟的时间。

或者您可以添加Custom Deserializer或Mixin批注,以避免在各处分层地添加默认构造函数。如此处所述:http://blogs.jbisht.com/blogs/2016/09/12/Deserialize-json-with-Java-parameterized-constructor。如果您有兴趣,请检查此内容。


似乎在此处重复> Spring HttpMessageNotReadableException

答案 2 :(得分:0)

确保满足以下条件:

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

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

答案 3 :(得分:0)

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

正如@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响应中具有属性 status paymentAmount 。这些零件可以轻松验证。

您可能会遇到类似-

的错误
  

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>