I have a method that makes a hit to external API and I have the exception handler is written to handle the errors and send the client-friendly response in case of errors. I have a requirement to test the non 200 OK responses from that external API such as Bad Request, Internal Server Error, and assert that the exception handler method should be invoked to send a client-friendly message. I am able to successfully mock the response of external API as Bad Request but it is not throwing the HttpStatusCodeException which is ideally thrown for 4xx status code and how can I verify method invocation of exception handler
private final RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
private final HttpHeaders httpHeaders = new HttpHeaders();
private final NotificationServiceImpl notificationService = new NotificationServiceImpl(restTemplate, httpHeaders, NOTIFICATION_API_URL, PRIMARY_NOTIFIERS, CC_NOTIFIERS, LANG, APPLICATION_NAME);
#Autowired
private ExceptionTranslator exceptionTranslator;
#Test
void testErrorOnSendNotification() {
Map<String, Instant> messages = Map.of("sample message", Instant.now());
ResponseEntity<HttpStatusCodeException> responseEntity =
new ResponseEntity<>(HttpStatus.BAD_REQUEST);
when(restTemplate.exchange(
ArgumentMatchers.anyString(),
ArgumentMatchers.any(HttpMethod.class),
ArgumentMatchers.any(),
ArgumentMatchers.<Class<HttpStatusCodeException>>any()))
.thenReturn(responseEntity);
// assertThrows(HttpStatusCodeException.class, () -> notificationService.sendNotification(messages));
verify(exceptionTranslator, times(1)).handleExceptions(any(), any());
}
#ExceptionHandler(Exception.class)
public ResponseEntity<Problem> handleExceptions(NativeWebRequest request, Exception error) {
Problem problem =
Problem.builder()
.withStatus(Status.BAD_REQUEST)
.withTitle(error.getMessage())
.withDetail(ExceptionUtils.getRootCauseMessage(error))
.build();
return create(error, problem, request);
}
You are mocking the restTemplate response. The actual #ExceptionHandler is not called at all. You are bypassing that layer.
In your case, in order to verify the ExceptionHandler, your service layer can be mocked, but the actual REST call has to proceed through, and a REAL response has to be triggered, in order for you to verify the Response Status Code + message.
Psuedo Code below:
#Service
class Service{
public void doSomeBusinessLogic() throws SomeException;
}
#RestController
class ControllerUsingService{
#AutoWired
private Service service;
#POST
public Response somePostMethidUsingService() throws SomeException{
service.doSomeBusinessLogic(someString);
}
}
#Test
void testErrorOnSendNotification() {
when(service.doSomeBusinessLogic(anyString()))
.thenThrow(SomeExceptionException.class);
Response receivedResponse = restTemplate.post(request, headers, etc);
//assert receivedResponse status code + message.
}
Hope that makes sense,
For further clarification:
By doing:
ResponseEntity<HttpStatusCodeException> responseEntity =
new ResponseEntity<>(HttpStatus.BAD_REQUEST);
when(restTemplate.exchange(
ArgumentMatchers.anyString(),
ArgumentMatchers.any(HttpMethod.class),
ArgumentMatchers.any(),
ArgumentMatchers.<Class<HttpStatusCodeException>>any()))
.thenReturn(responseEntity);
You are bypassing service layer and actually stating that whenever I make a request towards /API/xyz, then I should receive a BAD_REQUEST. That means whatever exception handling you have is going to be bypassed.
Related
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);
I am making rest template call to get the data from other microservice for this I am using the exchange method. This I am doing when a particular function gets called and below is the sample code for the same.
#Service
public void findUserById()
{
String username = "chathuranga";
String password = "123";
Integer userId = 1;
String url = "http://localhost:8080/users/" + userId;
//setting up the HTTP Basic Authentication header value
String authorizationHeader = "Basic " + DatatypeConverter.printBase64Binary((username + ":" + password).getBytes());
HttpHeaders requestHeaders = new HttpHeaders();
//set up HTTP Basic Authentication Header
requestHeaders.add("Authorization", authorizationHeader);
requestHeaders.add("Accept", MediaType.APPLICATION_JSON_VALUE);
//request entity is created with request headers
HttpEntity<AddUserRequest> requestEntity = new HttpEntity<>(requestHeaders);
ResponseEntity<FindUserResponse> responseEntity = restTemplate.exchange(
url,
HttpMethod.GET,
requestEntity,
FindUserResponse.class
);
// if (responseEntity.getStatusCode() == HttpStatus.OK) {
// System.out.println("response received");
System.out.println(responseEntity.getBody());
//} else {
// System.out.println("error occurred");
// System.out.println(responseEntity.getStatusCode());
//}
}
To handle the various exceptions code for example 500, 404 I want to made resttemplate builder class, (not the commented code) Which must be coded in different class for this I am referring this (custom hadler part)
I am not using try catch as it is not good approach when multiple calls happen in production environment.
I am also getting resource access exception while using exchange function which also needs to handle.
Now I am not getting how this class of custom handler should be called for handling response like 500.
If someone can help me with the sample code that would be very helpfull as I cannot test my code because it is not deployed for testing purpose till now
here is a sample
#ControllerAdvice
public class ErrorHandler {
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(ResourceAccessException.class)
public #ResponseBody
String handleResourceAccessException(
ResourceAccessException ex) {
return "internal server error";
}
}
When you use #ControllerAdvice , it will catch the exception you mention in #ExceptionHandler and here you can handle it the way you want.
If you don't want to return the response to the client right away, (for example, ignore ResourceAccessException and continue), you can override the handleError method of DefaultResponseErrorHandler, which is used by RestTemplate to handle the non 2xx codes.
public class ErrorHandler extends DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response, HttpStatus statusCode) {
// write your code here
}
}
By default Spring Boot maps /error to BasicErrorController. I want to log the exception along with the request that causes the exception. How can I get the original request in BasicErrorController or a new CustomErrorController. It seems that Spring Boot will make a new request to /error when an exception is thrown and the orginal request info is gone or no way to map the error with the original request.
Get it by:
String url = (String) request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
To avoid any misleading information, Spring Boot DOES NOT make a new request to /error endpoint. Instead, it wraps the exception in the original request and forwards it to /error endpoint. The request will be processed by BasicErrorHandler if you don't provide a custom error handler.
In this case, if you are using an interceptor, the interceptor will be invoked twice - one for the original request and the other for the forwarded request.
To retrieve the original request information, please look into the forwarded request's attributes. Basically, you can get the error message from these attributes javax.servlet.error.message, javax.servlet.error.status_code, org.springframework.web.servlet.DispatcherServlet.EXCEPTION.
And these are some resources that are related to error handling in Spring Boot:
spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
https://www.baeldung.com/exception-handling-for-rest-with-spring
https://www.baeldung.com/spring-boot-custom-error-page
If you are using controller advice to handle your exceptions then method with #ExceptionHandler can inject request as parameter, something like :
#ControllerAdvice
public class YourExceptionHandler
{
#ExceptionHandler
public ResponseEntity handleExceptions(HttpServletRequest request, Exception exception)
{
// use request to populate error object with details like requestId
LOGGER.debug(String.valueOf(request));
LOGGER.error(exception.getMessage(), exception);
}
}
Here is a working example:
#RestController
public class MyErrorController implements ErrorController {
private static final Logger LOG = LoggerFactory.getLogger(MyErrorController.class);
private static final String PATH = "/error";
private final ErrorAttributes errorAttributes;
public MyErrorController(ErrorAttributes errorAttributes) {
this.errorAttributes = errorAttributes;
}
#RequestMapping(value = PATH)
public ErrorDTO error(WebRequest webRequest, HttpServletRequest httpServletRequest) {
// Appropriate HTTP response code (e.g. 404 or 500) is automatically set by Spring.
Map<String, Object> attrs = errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.defaults());
LOG.warn("Forwarded Error Request: {} ", attrs.get("path"), (Throwable)
httpServletRequest.getAttribute("javax.servlet.error.exception"));
ErrorDTO dto = new ErrorDTO();
dto.message = (String) attrs.get("error");
dto.path = (String) attrs.get("path");
dto.timestamp = attrs.get("timestamp").toString();
return dto;
}
}
#Override
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException exception,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
OriginalRequestObject originalRequest = (OriginalRequestObject) exception.getBindingResult().getTarget();
ErrorResponse errorResponse = new ErrorResponse(
status.value(),
originalRequest.getId() + " " + exception.getMessage());
return ResponseEntity.status(status).body(myErrorResponse);
}
I have two microservices. The first one receives a call from the Frontend and then it calls to the second uService to receive some data. The last is returning an error response (Bad Request, this is ok - it is a use-case). However, I am losing the body (message) returned from the second microservice, as the first is throwing a HttpClientErrorException 400 null in the call
This is my code:
ResponseEntity<MyEntity> entityResponse = restTemplate.getForEntity(url, MyEntity.class, id);
I am not able to do entityResponse.getStatusCode() as an exception is thrown.
Handled it in the ControllerAdvice, my exception message is "400 null" even I return a custom message from the service.
So, I would like to get the response message sent in the called uservice to manage it.
Thanks in advance.
The answers here that explain how to catch the exception and access the body are correct. However, you may use a different approach. You can use a 3-d party library that sends Http request and handles the response. One of the well-known products would be Apache commons HTTPClient: HttpClient javadoc, HttpClient Maven artifact. There is by far less known but much simpler HTTPClient (part of an open source MgntUtils library written by me): MgntUtils HttpClient javadoc, MgntUtils maven artifact, MgntUtils Github. Using either of those libraries you can send your REST request and receive response independently from Spring as part of your business logic
What I'm doing in my project is the following.
MicroService_2 calls MicroService_1.
MicroService_1
MicroService_1 returns for example a HTTP 404 exception if the entity isn't found.
#RestController
#RequestMapping(value = "/api/v1/")
public class Service1Controller {
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public #ResponseBody MyEntity getMyEntity(#PathVariable String id) throws NotFoundException {
MyEntity result = ...
if(result == null) {
throw new NotFoundException("MyEntity [id: "+id+"] not found");
}
return result;
}
#ControllerAdvice
public class RestEndpointExceptionHandler extends RestExceptionHandler {
#ExceptionHandler(NotFoundException.class)
public ResponseEntity<String> handleNotFoundException(HttpServletRequest req, NotFoundException ex) throws NotFoundException {
return new ResponseEntity<String>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
}
}
MicroService_2
The MicroService_2 calls MicroService_1 and catches the exception by HTTP code and regenerate the NotFoundException.
#Override
public MyEntity getMyEntity(Principal principal) {
try {
ResponseEntity<MyEntity> entityResponse = restTemplate.getForEntity(url, MyEntity.class, id);
return entityResponse.getBody();
} catch(HttpClientErrorException e) {
HttpStatus status = e.getStatusCode();
if (status == HttpStatus.NOT_FOUND) {
throw new NotFoundException(e.getResponseBodyAsString()); // should be "MyEntity [id: {id}] not found"
} else {
throw new UnexpectedServerException(e.getResponseBodyAsString());
}
}
}
The Spring RestTemplate throws an error in case of 500 or 400 status codes. So if your second service responds with an error an exception will be thrown by the RestTemplate call in your first service.
HttpClientErrorException: in case of HTTP status 4xx
HttpServerErrorException: in case of HTTP status 5xx
UnknownHttpStatusCodeException: in case of an unknown HTTP status
To get the response message you could either catch the exception. E.g:
try {
ResponseEntity<MyEntity> entityResponse = restTemplate.getForEntity(url, MyEntity.class, id);
} catch(HttpStatusCodeException e) {
// e.getResponseBodyAsString();
}
or define a ResponseErrorHandler. The ResponseErrorHandler can be set during the instantiation of the RestTemplate. In the handleError method you will also be able to access the response message.
#Override
public void handleError(ClientHttpResponse httpResponse)
throws IOException {
}
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.