hades
hades

Reputation: 4696

Unit test MockHttpServletRequest not returning content type

I would like the application to return JSON object from my Java Classes (both success and fail cases).

I had defined a @RestControllerAdvice to handle the errors from controller. My program also shows the error message correctly in json, but the problem is in the unit test.

The problem is when it throws:

org.springframework.web.bind.MethodArgumentNotValidException

My unit test failed with error :

java.lang.AssertionError: Response header 'content-type' expected:<application/json;charset=UTF-8> but was:<null>

Controller:

@PostMapping("/import")
public ResponseEntity<StatusModel> import(@Valid @RequestBody ImportModel importModel ){
    //logic
    return new ResponseEntity<>(new StatusModel("Data accepted."), HttpStatus.OK);

}

Unit Test:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {MockConfiguration.class})
@WebAppConfiguration
public class ModelControllerTest {

    private MockMvc mockMvc;

    @InjectMocks
    private ModelController controller;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    }

    @Test
    public void import_validRequest_imported() throws Exception {

        mockMvc
            .perform(
                post("/import")
                    .content(VALID_CONTENT).contentType("application/json;charset=UTF-8"))
            .andExpect(status().isOk())
            .andExpect(header().string("content-type", "application/json;charset=UTF-8"))
            .andExpect(jsonPath("$.status", equalTo("Data accepted")));
    }

    @Test
    public void import_invalidRequest_notImported() throws Exception {    
        mockMvc
            .perform(
                post("/import")
                    .content(INVALID_CONTENT).contentType("application/json"))
            .andExpect(status().isBadRequest())
            .andDo(print())
            .andExpect(header().string("content-type", "application/json"));  <----- This assertion failed
    }   
}

MockHttpServletRequest log:

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

Handler:
             Type = com.test.ModelController
           Method = public org.springframework.http.ResponseEntity<com.model.StatusModel> com.ModelController.import(com.test.model.ImportModel)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = org.springframework.web.bind.MethodArgumentNotValidException

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

FlashMap:
       Attributes = null

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

Why is the content type, error message is empty?

Upvotes: 4

Views: 8308

Answers (1)

s7vr
s7vr

Reputation: 75914

Here is the rational why mock mvc doesn't support spring boot exception handlers followed by recommendation and fix.

Rational excerpt

Spring Boot's error handling is based on Servlet container error mappings that result in an ERROR dispatch to an ErrorController. MockMvc however is container-less testing so with no Servlet container the exception simply bubbles up with nothing to stop it.

So MockMvc tests simply aren't enough to test error responses generated through Spring Boot. I would argue that you shouldn't be testing Spring Boot's error handling. If you're customizing it in any way you can write Spring Boot integration tests (with an actual container) to verify error responses. And then for MockMvc tests focus on fully testing the web layer while expecting exceptions to bubble up.

This is a typical unit vs integration tests trade off. You do unit tests even if they don't test everything because they give you more control and run faster.

Recommendation excerpt

How can we write tests for error conditions using default spring-boot JSON responses, then?

@xak2000 Rossen's already covered this, but I wanted to give you a direct answer. If you really want to test the precise format of the error response then you can use an integration test using @SpringBootTest configured with a DEFINED_PORT or RANDOM_PORT web environment and TestRestTemplate.

Complete details here https://github.com/spring-projects/spring-boot/issues/7321

Fix

Here is slightly different error validation using Spring Boot test.

import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest(classes = DemoApplication.class,
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ModelControllerTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void import_invalidRequest_notImported() throws JSONException {

        String expected = "{\"status\":400,\"error\":\"Bad Request\",\"message\":\"JSON parse error: Unrecognized token 'Invalid': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false'); nested exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Invalid': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\\n at [Source: (PushbackInputStream); line: 1, column: 8]\",\"path\":\"/import\"}";

        String invalidJson = "Invalid";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(invalidJson, headers);

        ResponseEntity<String> response = restTemplate.exchange("/import", HttpMethod.POST, entity, String.class);

        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
        assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType());
        JSONAssert.assertEquals(expected, response.getBody(), false);

    }

}

Reference here https://mkyong.com/spring-boot/spring-rest-integration-test-example/

Upvotes: 3

Related Questions