Alain Duguine
Alain Duguine

Reputation: 475

Why MockMvc request retrieve empty responseBody while test succeed?

I'm trying to test my Spring Boot rest controller, to check if a request send propers errors if bean validation is failing.

I have a @RestController:

@RestController
@RequestMapping("/restaurants")
public class RestaurantsApiController {

    private final RestaurantService restaurantService;
    private final ProductRepository productRepository;
    private final ProductMapper productMapper;

    public RestaurantsApiController(RestaurantService restaurantService, ProductRepository productRepository, ProductMapper productMapper) {
        this.restaurantService = restaurantService;
        this.productRepository = productRepository;
        this.productMapper = productMapper;
    }

    @PostMapping("{id}/products")
    public ResponseEntity<ProductDto> addProduct(@PathVariable Long id,
                                                 @Valid @RequestBody ProductDto productDto){
        Product product = this.restaurantService.addProduct(id, productMapper.productDtoToProduct(productDto));
        return new ResponseEntity<>(productMapper.productToProductDto(product), HttpStatus.CREATED);
    }

I have a custom exceptionHandler with @ControllerAdvice annotation :

@ControllerAdvice
public class ExceptionControllerAdvice {
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public ResponseEntity<Object> validationException(MethodArgumentNotValidException ex, WebRequest request) {
        ....
// here i format my custom error message
        return new ResponseEntity<>(apiError, new HttpHeaders(), apiError.getStatus());
    }

which works perfectly and send me this custom response if a validation is failed :

{
    "status": "BAD_REQUEST",
    "errors": {
        "price": "doit être supérieur ou égal à 0",
        "name": "ne doit pas être nul",
        "category": "ne doit pas être nul"
    }
}

I'm trying to use mockMvc to test the behaviours with this test class :

@ExtendWith(MockitoExtension.class)
class RestaurantsApiControllerTest {

    @Mock
    private RestaurantService restaurantService;
    @Mock
    private ProductRepository productRepository;
    @Mock
    private ProductMapper productMapper;
    @InjectMocks
    private RestaurantsApiController controller;

    MockMvc mockMvc;

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

    @Test
    void givenInvalidFrom_whenAddProduct_ThenShouldThrowException() throws Exception {
        // productDto miss name, category and have negative value for price which is forbidden by validations annotations
        ProductDto productDto = ProductDto.builder().id(1L).price(-10.5D).build();

        MvcResult mvcResult = mockMvc.perform(post("/restaurants/1/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content(asJsonString(productDto)))
                .andExpect(status().isBadRequest())
                .andReturn();

        String result = mvcResult.getResponse().getContentAsString();

        then(restaurantService).shouldHaveNoInteractions();
    }

The test passes perfectly, i can see in the logs that the validation exception expect properly :

14:53:30.345 [main] WARN org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver - Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument ...
//I removed the rest of the message for readability, but each validation exception appears here properly
14:53:30.348 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Completed 400 BAD_REQUEST

but i can't find a way to test that my map of errors contains the fields that i'm expected. When i try to retrieve the response body with :

String result = mvcResult.getResponse().getContentAsString();

the string is empty, and i can't find any way of testing the response body.

I'm completely out of idea, some help would be greatly appreciated !

Thanks a lot !

Upvotes: 2

Views: 3278

Answers (2)

eg04lt3r
eg04lt3r

Reputation: 2610

When you are configuring the MockMvc instance using builder please make following updates:

MockMvcBuilders
    .standaloneSetup(controller)
    .setControllerAdvice(new ExceptionControllerAdvice())
    .build()

You should manually set the controller advice to mock mvc context, otherwise it is ignored. After this update you will receive body in error response. If you want to validate Json body, please use json path API as in answers above.

Upvotes: 5

Alex
Alex

Reputation: 866

MvcResult mvcResult = mockMvc.perform(post("/restaurants/1/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content(asJsonString(productDto)))
                .andExpect(status().isBadRequest())
                .andExpect(content().string("Your content"))
                .andReturn();   

Or use custom matcher

MvcResult mvcResult = mockMvc.perform(post("/restaurants/1/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content(asJsonString(productDto)))
                .andExpect(status().isBadRequest())
                .andExpect(content().string(new CustomMatcher()))
                .andReturn();

    private static class ContentMatcher extends CustomMatcher<String>{

            public ContentMatcher() {
                super("");
            }

            @Override
            public boolean matches(Object o) {
                final String expected="Some long wide string " +
                        "wich i should check" +
                        "...."
                return o.equals(expected);
            }
        }

Upvotes: 1

Related Questions