How to document #ControllerAdvice handled exception using Spring REST Docs - java

I have #ControllerAdvice annotated class, which is handling BadRequestException extends RuntimeException exception.
Now suppose that I have endpoint:
#PostMapping(value = "/createAccount")
public ResponseEntity<CreateAccountResponse> createAccount(#RequestBody #Valid CreateAccountRequest createAccountRequest) {...}
In case of unwanted scenario, endpoint throws BadRequestException (with HTTP Status 400) which constructs error JSON object as following:
{
"errorCode": 123,
"errorMessage: "Failure reason"
}
Is there any way to document case like this using Spring REST Docs ?
This is example of my approach:
#Test
public void createAccountFailExample() {
RestDocumentationResultHandler docs = document("create-acc-fail-example",
responseFields(
fieldWithPath("errorCode").type("Integer").description("Error code"),
fieldWithPath("errorMessage").type("String").description("Error message")
)
);
org.assertj.core.api.Assertions.assertThatThrownBy(() -> this.mockMvc.perform(RestDocumentationRequestBuilders.post("/createAccount")
.contextPath("/account")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(new CreateAccountRequest("nameTest", "surnameTest"))))
.andExpect(status().isBadRequest())
.andDo(docs)).hasCause(new BadRequestException(ServiceError.SOME_FAIL_REASON));
}
In this case test passes, but no documentation (.adoc) files are created.
When I try something like this:
ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.post("/createAccount")
.contextPath("/account")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(new CreateAccountRequest("testName", "testSurname"))))
.andExpect(status().isBadRequest())
.andDo(docs);
test fails because NestedServletException was thrown caused by BadRequestException, and again there is no documentation created.

I managed to solve the problem following this answer. When exception handler is defined for MockMvc, my second approach works as expected.

Related

How to validate Exception in spring-webflux WebTestClient tests?

How can I validate the actual Exception in a spring-webflux test?
The following worked in the old spring-web environment, but migrating to netty and spring-webflux, the MvcResult cannot be resolved anymore (NullPointerException):
#SpringBootTest
#AutoConfigureWebTestClient
public class ApiTest {
#Test
public void test() throws Exception {
webTestClient.get()
.uri("/api?test=123")
.exchange()
.expectStatus().isBadRequest()
.expectBody().consumeWith(rsp -> {
//throws NPE
Exception ex = ((MvcResult) rsp.getMockServerResult()).getResolvedException();
assertTrue(ex instanceof ResponseStatusException);
});
}
}
#RestController
public class ApiController {
#PostMapping("/api")
public String test(#RequestParam String test) {
if (test.matches("[0-9]+"))
throw new ResponseStatusException(HttpStatus.BadRequest, "Prohibited characters");
}
}
How could I still validate the real exception class?
The main purpose of the WebTestClient is to test endpoints using fluent API to verify responses. There is no magic deserialization or error handling happening but you can get access to the raw responses (status, headers, body).
In your example you will not get MvcResult or ResponseStatusException but you could get access to the raw body using rsp.getResponseBody() that would look like
{
"timestamp": "2022-05-17T17:57:07.041+00:00",
"path": "/api",
"status": 400,
"error": "Bad Request",
"requestId": "4fa648d"
}
You could use expectBody().consumeWith(rsp -> { ... }) to get access to the request and response or expectBody(String.class).value(body -> { ... })to get just body. As an alternative use some fluent API to validate result JSON.expectBody().json()or.expectBody().jsonPath()` to check specific fields only.
In addition you could still deserialize body explicitly using .expectBody(Response.class).value(body -> {...}).

How to assert the correct exception with TestRestTemplate?

I'm new to TestRestTemplate and with in Spring framework in general, and I'm trying to verify if a ResponseStatusException is thrown by my controller. For example the following degenerated request:
#RestController
public class UserManagementController {
#RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public ResponseEntity<UserDTO> updateUser(#RequestBody UserDTO userDTO, #PathVariable("id") String id){
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "user not found");
}
}
And in my test I'm using TestRestTemplate:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserManagementComponentTest {
#Autowired
private TestRestTemplate testRestTemplate;
#Test
public void testUpdateStaleUser() {
UserDTO updateUserDTORequest = UserDTO.builder();
assertThrows(ResponseStatusException.class,
() -> testRestTemplate.exchange("/users/" + createdUserId,
HttpMethod.PUT, new HttpEntity<>(updateUserDTORequest), UserDTO.class));
}
}
I expect to get ResponseStatusException, but the tests fails with the following message:
org.opentest4j.AssertionFailedError: Unexpected exception type thrown ==> expected: <org.springframework.web.server.ResponseStatusException> but was: <org.springframework.web.client.RestClientException>
I don't understand why RestClientException is thrown.
ResponseStatusException is the exception thrown in the server side and it will be handled by the spring-mvc framework in the server internally to return the suitable HTTP error response.
While TestRestTemplate just like a client-side REST library and hence it never can catch and handle the exception that is thrown internally from the API server.
TestRestTemplate can only throw its own exception when handling the HTTP response returned from calling an API. It will delegate to its internal RestTemplate 's ResponseErrorHandler for handling the error HTTP response.
So RestClientException is thrown by the ResponseErrorHandler that you configured for the TestRestTemplate.
Actually by default , the TestRestTemplate is configured to be fault tolerant such that it behaves in a test-friendly way by not throwing exceptions such that you can asserting directly on the returned HTTP status code or payload (see this) :
ResponseEntity<String> response = testRestTemplate.exchange("/users/" + createdUserId, HttpMethod.PUT, new HttpEntity<>(updateUserDTORequest), UserDTO.class));
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(response.getBody()).isEqualTo(xxxxx);

HttpMediaTypeNotAcceptableException for errors with text/plain message response?

I have a simple webservice that returns content either as json or as plain text (depending on the clients' accept http header).
Problem: if an error occurs during text/plain request, Spring somehow returns a 406 Not Acceptable. Which is kind of wrong, because spring could as well just write the error out as plain error text, and moreover should absolutely preserve the 400 error status:
#RestController
public class TestServlet {
#PostMapping(value = "/test", produces = {APPLICATION_JSON_VALUE, TEXT_PLAIN_VALUE, "text/csv"})
public Object post() {
throw new BadRequestException("bad req");
}
}
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
public BadRequestException(String msg) {
super(msg);
}
}
POST request with accept=application/json:
{
"timestamp": "2018-07-30T14:26:02",
"status": 400,
"error": "Bad Request",
"message": "bad req",
"path": "/test"
}
BUT with accept=text/csv (or text/plain) shows an empty response with status 406 Not Acceptable.
I also noticed the DispatcherServlet.processDispatchResult() is called twice: first with my BadRequest exception, 2nd time with HttpMediaTypeNotAcceptableException. So clearly the rendering of my custom exception fails, but why?
The problem is the restrictive Accept header allowing only one content type as response. In case of an error, Spring MVC needs to handle the BadRequestException and produce the required content type using a registered HttpMessageConverter.
By default Spring Boot has no message converter to produce text/plain directly from any object. You may register an ObjectToStringHttpMessageConverter (as a bean should work for Spring Boot) to allow this and you will get the result of BadRequestException.toString() as response body.
I assume a similar problem for text/csv but I am not sure how your setup for CSV message conversion looks like.
The condition written in "produces" determines the media type to use for the response to be "text/csv". So For a success scenario it works fine, **
but when you go for rendering an exception with a JSON body that
becomes a problem and gives you a 406 instead.
**
in latest versions of spring framework the problem fixes, but in old versions,as mentioned in Spring JIRA comments you should remove HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE attribute from request
the code might be like this :
#RestControllerAdvice
public class ExampleControllerAdvice {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<?> handleException(HttpServletRequest request, Exception e) {
request.removeAttribute(
HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
return new ResponseEntity<?>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
we can handle exception in advice
#ControllerAdvice
class ExceptionHandler{
#ExceptionHandler(value = {HttpMediaTypeNotAcceptableException.class})
public ResponseEntity handleMediaTypeException(HttpMediaTypeNotAcceptableException e) {
APIErrorResponse apiErrorResponse = new APIErrorResponse();
apiErrorResponse.setErrorCode("set custom code here");
apiErrorResponse.setErrorMessage("set custom meggage here/ here we can use message from object of exception i.e e.getMessage()");
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
}

how to return a response body when header is missing in spring REST api

I am passing a header to a spring REST api like:
#RequestHeader(value="test-header")
header is mandatory here for the API, so I do not want to keep it optional.
when no header is passed, any call to the API returns a standard 400 error indicating that request is syntantically wrong and then it does not enter the REST API. But, I want to construct a proper ResponseBody and return a json for this error. I am not sure about the best way to do this. I thought about using spring interceptor and check if this header was passed or not, but then I am not sure if I can create a responsebody from here. Atleast I could not figure out how to do so.
will interceptor approach work for this? If yes, how? If not, then what are the options? Can someone please help on this?
Update:
This is how the REST API is:
public void methodA(#RequestHeader(value="test-header") String header, #RequestBody User user, HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
...
...
}
When the header is present, it will enter the REST API and continue with the logic. But, if the header is not present, it does not enter the API and simply returns a standard 400 error.
The interceptor that I wrote is like:
public class XXXInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
...
...
return true;
}
}
STEP1: Use spring validator annotation like #valid to validate your request.
STEP 2: Write your custom validator class. which will be responsible to check the header and see if it has value or it has the expected value.
STEP 3: If the request is not correct validator throws your custom exception.
STEP 4: write your exception handler class. In the class define what response must me returned if the exception in STEP 3 is caught.
For more information on Exception Handling in Spring.
In our current projet we do use a java interceptor to authenticate the request but nothing beyound that.
Write a method with the annotation #ExceptionHandler and use ServletRequestBindingException.class as this exception is thrown in case of miss. You can return any type of object from this method.
For example
#ExceptionHandler(ServletRequestBindingException.class)
public ResponseEntity<ResponseObject> handleHeaderError(){
ResponseObject responseObject=new ResponseObject();
responseObject.setStatus(Constants.ResponseStatus.FAILURE.getStatus());
responseObject.setMessage(header_missing_message);
ResponseEntity<ResponseObject> responseEntity=new ResponseEntity<ResponseObject>(responseObject, HttpStatus.BAD_REQUEST);
return responseEntity;
}
Another approach would be using Spring Interceptors (HandlerInterceptorAdapter), as you mentioned in your question, with #ControllerAdvice and return your JSON in an #ExceptionHandler method.
Take a look at the following post: http://www.journaldev.com/2651/spring-mvc-exception-handling-exceptionhandler-controlleradvice-handlerexceptionresolver-json-response-example
This is coming late but then, a very straightforward way to deal with this type of issue is to use a Controller Advice class which allows you to handle exceptions across the whole application in one global handling component.
The exception throw by spring is the MissingRequestHeaderException which you can then provide a custom handler in your controller advice class.
#Slf4j
#RestControllerAdvice
public class ControllerAdvice {
#ExceptionHandler(MissingRequestHeaderException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public ErrorResponse handleMissingRequestHeaderException(MissingRequestHeaderException ex) {
log.error(ex.getMessage(), ex);
return new ErrorResponse("Missing request header: " + ex.getHeaderName());
}
}
public class ErrorResponse implements Serializable {
private String message;
public ErrorResponse(String message) {
this.message = message;
}
}

How should behave rest controller when during a processing occur a error

I have question that interest me.
Assume that I have some rest controller and some rest client writing in javascript. This client send request to a controller and during a processing occur some error. How should behave controller in this situation? Should return null? or string with message?
For example, We have controller like this:
#RequestMapping("/user", method = RequestMethod.POST)
public #ResponseBody String createUser(User user) {
try {
userService.create(user);
} catch(UserCreationException e) {
}
}
This is very simple example but is many different examples of controllers like controller which return some resources or only change state on the server side and I don't know what to do when occur error.
in improving developer(your consumers) experience , it is a good idea to respond with appropriate error messages on the response body in addition to the Http status code.
Here is an example with spring, mainly throw an exception that you can deal with by extending ResponseEntityExceptionHandler #ControllerAdvice
#ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException{
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(String message) {
super(message);
}
}
#Controller
#RequestMapping("/XXXXXs")
public class DoctypesController {
#RequestMapping( method = RequestMethod.GET , value="/xxx")
public ResponseEntity<?> getXXXXXX(HttpServletRequest request) {
if (XXX == null ) {
throw new ResourceNotFoundException("XXXX Not found for);
}else{
response = buildResponse(xxxx)
}
return response;
}
}
#ControllerAdvice
public class XXXXEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { ResourceNotFoundException.class })
protected ResponseEntity<Object> handleMissingResource(RuntimeException ex, final WebRequest request) {
HttpStatus status = HttpStatus.NOT_FOUND;
return new ResponseEntity<Object>(new Error(String.valueOf(status.value()), status.getReasonPhrase(),ex.getMessage()),status);
}
}
According http specifications, the server must return a error code >= 500 in case of internal error during processing.
If the error is caused because the client did a wrong request : the server must return a error code >= 400 and < 500
Of course, on client side you must take care to handle those errors properly (i.e. displaying a friendly error message or something like that).
You should really use the HTTP Error codes and handle the HTTP error codes using your client-side technology, ie. JavaScript in your case.
For example: given a user who is unauthorised to read/access a Resource, then the 403 error code should be returned to the client. By using the standard HTTP/REST Error codes, you conform to an API that can be understood by any client, whether JavaScript or something else.
With Spring MVC and Rest controllers, it's really easy. Create a simple class for your Exception and annotate the class with the HTTP Error code, e.g. #ResponseStatus(value = HttpStatus.FORBIDDEN) for a 403 error. Then in your Controller, you can throw the exception which would in turn return the HTTP error code.

Categories

Resources