Reputation: 475
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
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
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