Testing Bad Request while posting an incomplete entity with Spring Boot - spring-boot

I'm trying to test the creation of an entity with incomplete data as follows:
#Test
public void postVisitor_withIncompleteData_shouldFailWithBadRequest() throws Exception {
Visitor emptyVisitor = new Visitor();
mockMvc.perform(post("/visits")
.contentType(APPLICATION_JSON)
.content(objectMapper.writeValueAsString(visitor))
).andExpect(status().isBadRequest());
}
But my test fails with this error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.validation.ConstraintViolationException: Validation failed for classes [com.reweb.Visitor] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='cannot be empty', propertyPath=email, rootBeanClass=class com.reweb.Visitor, messageTemplate='{javax.validation.constraints.NotEmpty.message}'}
]
How can I test this is a bad request?
Adding #Test(expected = ConstraintViolationException.class) wouldn't work either.

If you have already configured #Valid with #ResponseBody in controller , then you need to create ExceptionHandlerInterceptor just like below:
#RestControllerAdvice
#Slf4j
public class ApplicationExceptionInterceptor extends ExceptionHandlerExceptionResolver {
#ExceptionHandler(Throwable.class)
public
ResponseEntity<Response<?>>
handleControllerException(final HttpServletRequest request, final Throwable ex) {
if (ex instanceof MethodArgumentNotValidException) {
log.error(ex.getMessage());
final MethodArgumentNotValidException exception = (MethodArgumentNotValidException) ex;
final List<ObjectError> errors = exception.getBindingResult().getAllErrors();
final List<FaultDetail> faultDetailsList = new ArrayList<>();
final List<String> error = new ArrayList<>();
errors.forEach(action -> error.add(action.getDefaultMessage()));
faultDetailsList.add(new FaultDetail(error));
}
return ResponseEntity.body(faultList).status(HttpStatus.BAD_REQUEST).build();
}
You also need to provide to provide this ExceptionHandler to MockMvc just like below ,I am providing for RestAssuredMockMvc there should be similiar configuation for MockMvc as well:
#BeforeMethod
public void initializeMockMvcWithControllerAdvice(Object controller, Object controllerAdvice, ContentType contentType) {
RestAssuredMockMvc.reset();
RestAssuredMockMvc.mockMvc(MockMvcBuilders.standaloneSetup(new Object[]{controller}).setControllerAdvice(new Object[]{controllerAdvice}).build());
RestAssuredMockMvc.requestSpecification = (new MockMvcRequestSpecBuilder()).setContentType(contentType).build();
}

Related

How to handle exceptions thrown in the service layer?

I'm working on a spring-boot application. I tried handling exceptions .But i guess there is something wrong about how I'm doing it because it always throws internal server error 500.
I tried setting up custom exception classes and also used response status codes with #ResponseStatus. But regardless of what the exception is it throws an internal server error only.
I'm using intellij and the message i've given in the exception is printed there but the response body is empty.This i guess must be because it is throwing an internal server error.
Controller class
#RequestMapping(value = "/attendance",method = RequestMethod.POST)
public ResponseEntity<?> enterAttendance(#RequestBody ViewDTO viewDTO) throws CustomException{
return new ResponseEntity<>(tempResultServices.handleAttendance(viewDTO),HttpStatus.OK);
}
}
Service layer
#Override
public TempResult handleAttendance(ViewDTO viewDTO) throws CustomException {
TempIdentity tempIdentity=new TempIdentity();
tempIdentity.setRegistrationNo(viewDTO.getRegistrationNo());
tempIdentity.setCourseId(viewDTO.getCourseId());
tempIdentity.setYear(viewDTO.getYear());
tempIdentity.setSemester(viewDTO.getSemester());
User user=userService.findByUserId(viewDTO.getUserId());
tempIdentity.setUser(user);
if(!viewDTO.isAttendance()){
TempResult tempResultUser =new TempResult(tempIdentity,viewDTO.isAttendance(),0);
ResultIdentity resultIdentity=new ResultIdentity(tempIdentity.getRegistrationNo(),tempIdentity.getCourseId(),tempIdentity.getYear(),tempIdentity.getSemester());
Result result=new Result(resultIdentity,0,"E*");
AttendanceDraft attendanceDraft=atteDraftService.findDraft(viewDTO.getRegistrationNo(),viewDTO.getCourseId(),viewDTO.getYear(),viewDTO.getSemester(),viewDTO.getUserId());
if(attendanceDraft!=null){
attendanceDraft.setStatus(true);
atteDraftService.save(attendanceDraft);
//atteDraftService.delete(attendanceDraft);
tempResultRepository.save(tempResultUser);
resultRepository.save(result);
return tempResultUser;
}
else{
throw new CustomException("No draft available");
}
}
else{
TempResult tempResultUser =new TempResult(tempIdentity,viewDTO.isAttendance());
AttendanceDraft attendanceDraft=atteDraftService.findDraft(viewDTO.getRegistrationNo(),viewDTO.getCourseId(),viewDTO.getYear(),viewDTO.getSemester(),viewDTO.getUserId());
if(attendanceDraft!=null){
attendanceDraft.setStatus(true);
atteDraftService.save(attendanceDraft);
//atteDraftService.delete(attendanceDraft);
tempResultRepository.save(tempResultUser);
return tempResultUser;
}
else{
throw new CustomException("No draft available");
}
}
}
The exception class
#ResponseStatus(code= HttpStatus.NOT_FOUND)
public class CustomException extends RuntimeException {
public CustomException(String message){
super(message);
}
}
The terminal in the intellij prints "No draft available ". But i want it not as an internal server error.
Can some one tell me how i should be handling these errors please?
I tried using the #RestControllerAdvice
#RestControllerAdvice
public class WebRestControllerAdvice {
#ExceptionHandler(CustomException.class)
public ResponseMsg handleNotFoundException(CustomException ex) {
ResponseMsg responseMsg = new ResponseMsg(ex.getMessage());
return responseMsg;
}
}
And this is my response message class
public class ResponseMsg {
private String message;
//getters and setters
}
This is another simple request in the application
#RequestMapping(value = "/user/view",method = RequestMethod.POST)
public ResponseEntity<?> getUser(#RequestBody UserDTO userDTO) throws CustomException{
User user=userService.findByUsername(userDTO.getUsername());
if(user!=null){
return ResponseEntity.ok(user);
}
//
throw new CustomException("User not found");
}
But still the custom exception is not thrown. The response body is empty. but intellij says "user not found" and postman returns the status code 500.
Spring boot has a very convenient way to handle exceptions in any layer of your application which is defining a #ControllerAdvice bean. Then you can throw any type of exception in your code and it will be "captured" on this class.
After this you can handle and return whatever your app needs to return.
By the way, you can return your custom object and it will be parsed to json automatically.
Documentation: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/
Sample code:
#ControllerAdvice
public class ErrorHandler {
#ExceptionHandler(BadRequestException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public Object processValidationError(BadRequestException ex) {
//return whatever you need to return in your API
}
}

Spring boot rest request data type validation

I am doing validation for request object in spring boot rest. I have to validate data type of request. The request has multiple boolean values and trying to validate if string in passed for boolean data type.
I have handling HttpMessageNotReadableException in my ControllerAdvice class and sending list of error message. But in my response only first field is throwing exception. If clue ,please help.
#Vishnu Dubey use this .....
#RestControllerAdvice
public class ServiceControllerAdvice {
private static final Logger log = LoggerFactory.getLogger(ServiceControllerAdvice.class);
#ExceptionHandler(value = { ConstraintViolationException.class })
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public ServiceResponse<?> constraintViolationException(final ConstraintViolationException ex) {
log.error("Validation failed", ex);
final ServiceResponse<?> response = new ServiceResponse<>(-1);
final Error error = new Error();
error.setCode("PS01");
error.setContext(ex);
error.setMessage(ex.getMessage());
response.setError(error);
return response;
}
}

spring mockMVC testing method GET

i created post method in mockMVC (in spring boot project)
This is my method testing
This is my method testing
#Test
public void createAccount() throws Exception {
AccountDTO accountDTO = new AccountDTO("SAVINGS", "SAVINGS");
when(addaccountService.findByName("SAVING")).thenReturn(Optional.empty());
when(addaccountService.createAccount(any())).thenReturn(createdAccountDTO);
CreatedAccountDTO createdAccountDTO = new CreatedAccountDTO("a#wp.pl", "SAVINGS", "1234rds", uuid);
mockMvc.perform(
post("/account").contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(AccountNewDTO)))
.andExpect(status().isCreated())
.andExpect(header().string("location", containsString("/account/"+uuid.toString())));
System.out.println("aaa");
}
I want to write GET method.
how to write a get method in mock mvc? how to verify whether what I threw was returned?
You can try the below for Mockmvc perform get and post methods
For get method
#Autowired
private MuffinRepository muffinRepository;
#Test
public void testgetMethod throws Exception(){
Muffin muffin = new Muffin("Butterscotch");
muffin.setId(1L);
BddMockito.given(muffinRepository.findOne(1L)).
willReturn(muffin);
mockMvc.perform(MockMvcRequestBuilders.
get("/muffins/1")).
andExpect(MockMvcResutMatchers.status().isOk()).
andExpect(MockMvcResutMatchers.content().string("{\"id\":1, "flavor":"Butterscotch"}"));
}
//Test to do post operation
#Test
public void testgetMethod throws Exception(){
Muffin muffin = new Muffin("Butterscotch");
muffin.setId(1L);
BddMockito.given(muffinRepository.findOne(1L)).
willReturn(muffin);
mockMvc.perform(MockMvcRequestBuilders.
post("/muffins")
.content(convertObjectToJsonString(muffin))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResutMatchers.status().isCreated())
.andExpect(MockMvcResutMatchers.content().json(convertObjectToJsonString(muffin)));
}
If the response is empty then make sure to override equals() and hashCode() method on the Entity your repository is working with
//Converts Object to Json String
private String convertObjectToJsonString(Muffin muffin) throws JsonProcessingException{
ObjectWriter writer = new ObjectWriter().writer().withDefaultPrettyPrinter();
return writer.writeValueAsString(muffin);
}
You can use the static get method of the class MockMvcRequestBuilders, see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.html#get-java.lang.String-java.lang.Object...-
Example:
mockMvc.perform(get("/account")).andExpect(...);
If you throw an exception within your controller method it will typically trigger execution of an exception handler which transforms the exception into a HTTP error response. By default, you could check if the status of the response was 500. If you have implemented your own exception handler you may want to check the response body as well to verify if it contains the expected error data.

Empty Exception Body in Spring MVC Test

I am having trouble while trying to make MockMvc to include the exception message in the response body. I have a controller as follows:
#RequestMapping("/user/new")
public AbstractResponse create(#Valid NewUserParameters params, BindingResult bindingResult) {
if (bindingResult.hasErrors()) throw BadRequestException.of(bindingResult);
// ...
}
where BadRequestException looks sth like this:
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "bad request")
public class BadRequestException extends IllegalArgumentException {
public BadRequestException(String cause) { super(cause); }
public static BadRequestException of(BindingResult bindingResult) { /* ... */ }
}
And I run the following test against /user/new controller:
#Test
public void testUserNew() throws Exception {
getMockMvc().perform(post("/user/new")
.param("username", username)
.param("password", password))
.andDo(print())
.andExpect(status().isOk());
}
which prints the following output:
Resolved Exception:
Type = controller.exception.BadRequestException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 400
Error message = bad request
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Does anybody have an idea on why is Body missing in the print() output?
Edit: I am not using any custom exception handlers and the code works as expected when I run the server. That is, running the application and making the same request to the server returns back
{"timestamp":1423076185822,
"status":400,
"error":"Bad Request",
"exception":"controller.exception.BadRequestException",
"message":"binding failed for field(s): password, username, username",
"path":"/user/new"}
as expected. Hence, there is a problem with the MockMvc I suppose. It somehow misses to capture the message field of the exception, whereas the default exception handler of the regular application server works as expected.
After opening a ticket for the issue, I was told that the error message in the body is taken care of by Spring Boot which configures error mappings at the Servlet container level and since Spring MVC Test runs with a mock Servlet request/response, there is no such error mapping. Further, they recommended me to create at least one #WebIntegrationTest and stick to Spring MVC Test for my controller logic.
Eventually, I decided to go with my own custom exception handler and stick to MockMvc for the rest as before.
#ControllerAdvice
public class CustomExceptionHandler {
#ExceptionHandler(Throwable.class)
public #ResponseBody
ExceptionResponse handle(HttpServletResponse response, Throwable throwable) {
HttpStatus status = Optional
.ofNullable(AnnotationUtils.getAnnotation(throwable.getClass(), ResponseStatus.class))
.map(ResponseStatus::value)
.orElse(HttpStatus.INTERNAL_SERVER_ERROR);
response.setStatus(status.value());
return new ExceptionResponse(throwable.getMessage());
}
}
#Data
public class ExceptionResponse extends AbstractResponse {
private final long timestamp = System.currentTimeMillis();
private final String message;
#JsonCreator
public ExceptionResponse(String message) {
checkNotNull(message, "message == NULL");
this.message = message;
}
}
This likely means that you either didn't handle the exception or you've really left the body empty. To handle the exception either add an error handler in the controller
#ExceptionHandler
public #ResponseBody String handle(BadRequestException e) {
return "I'm the body";
}
or user the global error handler if you're on 3.2 or above
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler
public #ResponseBody String handleBadRequestException(BadRequestException ex) {
return "I'm the body";
}
}
with this the body will be populate, you should populate it with your error message
Updated solution:
If you don't want to do a full integration test but still want to make sure the message is as expected, you can still do the following:
String errorMessage = getMockMvc()
.perform(post("/user/new"))
...
.andReturn().getResolvedException().getMessage();
assertThat(errorMessage, is("This is the error message!");

Custom json response for internal exception in spring

While implementing a global exception handler in Spring, I noticed that in case of a not recognized Accept header, Spring would throw it's own internal error. What I need is to return a custom JSON error structure instead. Works fine for application specific exceptions and totally fails for Spring HttpMediaTypeNotAcceptableException.
This code tells me "Failed to invoke #ExceptionHandler method: public java.util.Map RestExceptionHandler.springMalformedAcceptHeaderException()" when I try to request a page with incorrect Accept header. Any other way to return custom JSON for spring internal exceptions?
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(value = HttpMediaTypeNotAcceptableException.class)
#ResponseBody
public Map<String, String> springMalformedAcceptHeaderException() {
Map<String, String> test = new HashMap<String, String>();
test.put("test", "test");
return test;
}
}
Eventually figured that the only way is to do the json mapping manually.
#ExceptionHandler(value = HttpMediaTypeNotAcceptableException.class)
#ResponseBody
public String springMalformedAcceptHeaderException(HttpServletResponse response) {
// populate errorObj, set response headers, etc
ObjectWriter jsonWriter = new ObjectMapper().writer();
try {
return jsonWriter.writeValueAsString(errorObj);
} catch(Exception e){}
return "Whatever";
}

Resources