I am testing return values in a method but need to also test exceptions.
Below is a code snippet of one of the exceptions - how should I test this ?
#Override
public Response generateResponse(Request request) throws CustomException {
try {
GenerateResponse response = client.generateResponse(headers, generateRequest);
return response;
} catch (FeignException.BadRequest badRequest) {
String message = "Received Bad Request";
throw new CustomException(message, "" + badRequest.status());
} catch (FeignException.Unauthorized unauthorized) {
log.error(unauthorized.contentUTF8());
String message = "Received UnAuthorized Exception ";
throw new CustomException(message, "" + unauthorized.status());
}
}}
I have tested the happy path for the method I am testing using the following:
Mockito.when(service.getResponse(Mockito.any(), Mockito.any())).thenReturn(getResponse);
Mockito.when(service.getResponse(Mockito.any(), Mockito.any())).thenThrow(new CustomException());
If you want the mock to throw an error, you do not want thenReturn but thenThrow
Related
I have a piece of code to send a request via WebClient:
public String getResponse(String requestBody){
...
final WebClient.RequestHeadersSpec<?> request =
client.post().body(BodyInserters.fromValue(requestBody));
final String resp =
req.retrieve().bodyToMono(String.class)
.doOnError(
WebClientResponseException.class,
err -> {
// do something
throw new InvalidRequestException(err.getResponseBodyAsString());
})
.block();
...
}
From the code, it looks like the InvalidRequestException will be thrown when the WebClientResponseException happens. However, the test throws the WebClientResponseException instead of the InvalidRequestException.
Here's the part of unit test I wrote to cover the .doOnError part. I tried the following:
...
when(webClientMock.post()).thenReturn(requestBodyUriMock);
when(requestBodyUriMock.body(any())).thenReturn(requestHeadersMock);
when(requestHeadersMock.retrieve()).thenReturn(responseMock);
when(responseMock.bodyToMono(String.class)).thenThrow(new WebClientResponseException(400, "Bad Request", null, null, null));
try {
String result = someServiceSpy.getResponse(requestBody);
} catch (InvalidRequestException e) {
assertEquals(expectedCode, e.getRawStatusCode());
}
The solution is I need to wrap the exception in Mono.error, and then the doOnError will be triggered.
when(responseMock.bodyToMono(String.class)).thenReturn(Mono.error(thrownException));
I am calling an external API from my code using RestTemplate like below:
try {
responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity,
UploadResonse.class);
} catch (BusinessException ex) {
fetchErrorResponseEntity = ex.getResponseEntity();
if (fetchErrorResponseEntity.getStatusCodeValue() == 404) {
throw new BusinessException(ex.getMessage(), ErrorResponse.NOT_FOUND);
} else if (fetchErrorResponseEntity.getStatusCodeValue() == 500) {
throw new BusinessException(ex.getMessage(),
ErrorResponse.INTERNAL_SERVER_ERROR);
} else if (fetchErrorResponseEntity.getStatusCodeValue() == 400) {
throw new BusinessException(ex.getMessage(), ErrorResponse.INVALID_REQUEST);
}
}
This API call is returning 200 Success but when I debug it, it still goes to handleResponse(URI url, HttpMethod method, ClientHttpResponse response) method of RestTemplate.class
And then it's coming to my RestTemplateErrorHandler.java file
#Override
public boolean hasError(ClientHttpResponse clientHttpResponse)
throws IOException {
return clientHttpResponse.getStatusCode() != HttpStatus.OK;
}
#Override
public void handleError(ClientHttpResponse clientHttpResponse)
throws IOException {
String errMessage = getErrMessage(clientHttpResponse);
HttpStatus status = clientHttpResponse.getStatusCode();
switch (status) {
case BAD_REQUEST: // 400
throw new BusinessException(errMessage,
ErrorResponse.INVALID_REQUEST);
case NOT_FOUND:
throw new BusinessException(errMessage, ErrorResponse.NOT_FOUND);
case SERVICE_UNAVAILABLE: // 503
throw new BusinessException(errMessage, ErrorResponse.TIME_OUT);
case METHOD_NOT_ALLOWED: // 405
case INTERNAL_SERVER_ERROR: // 500
default:
throw new BusinessException(errMessage,
ErrorResponse.INTERNAL_SERVER_ERROR);
}
}
Can someone lease help me to understand if it's the correct behaviour.
I suspect that if the response is 200 Success it should not go to the RestTemlate.class and RestTemplateErrorHandler.class
This behaviour is creating problem when API return 201 Created status, that time it goes to handleError() method and return the default case INTERNAL_SERVER_ERROR
Can someone please help me here
The following code will call the error handler every time the response is not 200 OK, event if it is successful like 201 Created.
#Override
public boolean hasError(ClientHttpResponse clientHttpResponse)
throws IOException {
return clientHttpResponse.getStatusCode() != HttpStatus.OK;
}
Try changing the implementation to the following:
#Override
public boolean hasError(ClientHttpResponse clientHttpResponse)
throws IOException {
return !clientHttpResponse.getStatusCode().is2xxSuccessful();
}
This is better suited for your needs as it will consider all 2xx status as successful requests instead of only 200 OK.
According to documentation method handleResponse() as it name suggests will handle the given response, perform appropriate logging and invoke the ResponseErrorHandler (if needed) which is interface used by the RestTemplate to determine whether a particular response has an error or not.
RestTemplateErrorHandler class implements implements ResponseErrorHandler.
If the hasError() method returns true then Spring will automatically call the handleError() method. This is the flow.
If you check implementation for handleResponse method, given below, you will see that there is a call to hasError method to check if the response has any errors. Default implementation of hasError method will return true is response code is 4XX or 5XX. If there is no errors, method will proceed execution and handleError method won't be invoked, as I explained above.
protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
ResponseErrorHandler errorHandler = this.getErrorHandler();
boolean hasError = errorHandler.hasError(response);
if (this.logger.isDebugEnabled()) {
try {
this.logger.debug(method.name() + " request for \"" + url + "\" resulted in " + response.getRawStatusCode() + " (" + response.getStatusText() + ")" + (hasError ? "; invoking error handler" : ""));
} catch (IOException var7) {
;
}
}
if (hasError) {
errorHandler.handleError(url, method, response);
}
}
In code you posted hasError will return true for all response codes that are different from 200. That's why handleError is invoked.
I have a controller somewhere like this:
#GetMapping(value = "redirect")
public ModelAndView oauthRedirect() {
try {
if (true) {
serviceOAuthMetrics.addRedirectCookieException();
throw new AuthenticationChainException("User or client is null in state token");
}
return new ModelAndView(REDIRECT + redirectUrl + "closewindow.html?connected=true");
} catch (Exception e) {
return new ModelAndView(REDIRECT + redirectUrl + "closewindow.html?connected=false");
}
}
I'm trying to test it like this:
#Test
void oauthRedirectThrowsExceptionUserIdIsNullTest() throws Exception {
RequestBuilder request = MockMvcRequestBuilders
.get("/oauth/redirect")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON);
mockMvc.perform(request)
.andExpect(redirectedUrl("http://localhost:8080/closewindow.html?connected=false"))
//.andExpect(content().string("AuthenticationChainException: User or client is null in state token"))
.andReturn();
}
The tests pass as it asserts the return piece from the catch block. However, I'm not seeing a way to assert which exception was thrown and what is the message inside it? (the line commented out fails the test when uncommented).
Thank you.
You cannot test for the Java exception in the traditional sense. You need to test for the response status, and body if you need to. MockMVC is mocking the HTTP request / response.
I am using mockito to mock my authenticationService.getUserInfo method.
I am having difficulty in try to mock HttpClientErrorException.Unauthorized.
I can't just new HttpClientErrorException.Unauthorized.
Is there any other way?
Implementation:
try {
result = authenticationService.getUserInfo(accessToken);
} catch (HttpClientErrorException.Unauthorized e) {
String errorMsg = "Invalid access token - " + e.getMessage();
throw new InvalidAccessTokenException(errorMsg);
}
Test case:
#Test
public void givenInvalidToken_whenGetUserInfoThrows401Response_thenThrowInvalidAccessTokenExceptionAndFail() throws ServletException, IOException {
HttpClientErrorException ex = new HttpClientErrorException(HttpStatus.UNAUTHORIZED);
exceptionRule.expect(InvalidAccessTokenException.class);
exceptionRule.expectMessage("Invalid access token - " + ex.getMessage());
Mockito.when(authenticationService.getUserInfo(anyString())).thenThrow(ex);
filter.doFilterInternal(this.request, this.response, this.mockChain);
}
Error logs from running test case:
java.lang.AssertionError:
Expected: (an instance of com.demo.security.common.exception.InvalidAccessTokenException and exception with message a string containing "Invalid access token - 401 UNAUTHORIZED")
but: an instance of com.demo.security.common.exception.InvalidAccessTokenException <org.springframework.web.client.HttpClientErrorException: 401 UNAUTHORIZED> is a org.springframework.web.client.HttpClientErrorException
Stacktrace was: org.springframework.web.client.HttpClientErrorException: 401 UNAUTHORIZED
HttpClientErrorException.Unauthorized is child exception of HttpStatusCodeException
public static final class HttpClientErrorException.Unauthorized
extends HttpClientErrorException
So when you throw HttpStatusCodeException the catch block will not executed since child exception will not catch parent exception.
So create HttpClientErrorException.Unauthorized and throw it
HttpClientErrorException http = HttpClientErrorException.Unauthorized.create(HttpStatus.UNAUTHORIZED, null, null, null, null);
I would also recommend to catch HttpStatusCodeException since UNAUTHORIZED is specific to 401 and any 400 series exceptions will not be caught by UNAUTHORIZED. You can also get status code from HttpClientErrorException and verify it as shown in my answer
try {
result = authenticationService.getUserInfo(accessToken);
} catch (HttpClientErrorException e) {
if(e.getStatusCode().equals(HttpStatus.UNAUTHORIZED))
//do something
String errorMsg = "Invalid access token - " + e.getMessage();
throw new InvalidAccessTokenException(errorMsg);
}
TL;DR: #ExceptionHandler function is returning 200 OK instead of 400 Bad Request for a MissingServletParameterException when calling HttpServletResponse.getStatus & HttpStatus.valueOf(HttpServletResponse.getStatus)).name(). MissingServletParameterException is only used as an example, also happens for other exceptions too.
Hello,
The issue I'm having is that I'm trying to integrate Raygun (a crash reporting system) with our Java/Spring Boot application. The easiest way I've found was to create a custom exception handler that would display the error message to the user as well as pass the exception to Raygun.
Originally, I tried the implementation suggested here with my own Raygun implementation added https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
#ControllerAdvice
class GlobalDefaultExceptionHandler {
public static final String DEFAULT_ERROR_VIEW = "error";
private static ApiAccessToken accessToken = new ApiAccessToken();
private static String databaseName = null;
#ExceptionHandler(value = Exception.class)
public ModelAndView
defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
// If the exception is annotated with #ResponseStatus rethrow it and let
// the framework handle it
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
// Otherwise setup and send the user to a default error-view.
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
// Display the error message to the user, and send the exception to Raygun along with any user details provided.
RaygunClient client = new RaygunClient("<MyRaygunAPIKey>");
if (accessToken.getUsername() != null && accessToken.getDatabaseName() != null) {
ArrayList tags = new ArrayList<String>();
tags.add("username: " + accessToken.getUsername());
tags.add("database: " + accessToken.getDatabaseName());
client.Send(e, tags);
accessToken = null;
return mav;
} else if (databaseName != null) {
ArrayList tags = new ArrayList<String>();
tags.add("database: " + databaseName);
client.Send(e, tags);
databaseName = null;
return mav;
} else {
client.Send(e);
return mav;
}
}
The problem I encountered with this is that we have both public and private API endpoints. The private API endpoints are used for our iOS applications, whereas the public API endpoints have no front-end. They were designed for businesses to be able to integrate into their own systems (PowerBI, Postman, custom integrations, etc). And so there is no views that I can redirect to using ModelAndView.
Instead, what I've decided to do is instead of using ModelAndView, I'm just returning a string that has been formatted to mimic Spring's default JSON error message.
#ExceptionHandler(value = Exception.class)
public #ResponseBody String defaultErrorHandler(HttpServletRequest req, HttpServletResponse resp, Exception e) throws Exception {
// Create a customised error message that imitates the Spring default Json error message
StringBuilder sb = new StringBuilder("{ \n")
.append(" \"timestamp\": ").append("\"").append(DateTime.now().toString()).append("\" \n")
.append(" \"status\": ").append(resp.getStatus()).append(" \n")
.append(" \"error\": ").append("\"").append(HttpStatus.valueOf(resp.getStatus()).name()).append("\" \n")
.append(" \"exception\": ").append("\"").append(e.getClass().toString().substring(6)).append("\" \n")
.append(" \"message\": ").append("\"").append(e.getMessage()).append("\" \n")
.append(" \"path\": ").append("\"").append(req.getServletPath()).append("\" \n")
.append("}");
String errorMessage = String.format(sb.toString());
// Display the error message to the user, and send the exception to Raygun along with any user details provided.
RaygunClient client = new RaygunClient("<MyRaygunAPIKey>");
if (accessToken.getUsername() != null && accessToken.getDatabaseName() != null) {
ArrayList tags = new ArrayList<String>();
tags.add("username: " + accessToken.getUsername());
tags.add("database: " + accessToken.getDatabaseName());
client.Send(e, tags);
accessToken = null;
return errorMessage;
} else if (databaseName != null) {
ArrayList tags = new ArrayList<String>();
tags.add("database: " + databaseName);
client.Send(e, tags);
databaseName = null;
return errorMessage;
} else {
client.Send(e);
return errorMessage;
}
}
The only issue with this is that when I purposefully cause an exception to be thrown, it returns with a HTTP status of 200 OK which is obviously not correct.
For instance, this is with defaultErrorHandler() commented out (sends nothing to Raygun):
{
"timestamp": "2017-07-18T02:59:45.131+0000",
"status": 400,
"error": "Bad Request",
"exception":
"org.springframework.web.bind.MissingServletRequestParameterException",
"message": "Required String parameter ‘foo’ is not present",
"path": "/api/foo/bar/v1"
}
And this is with it not commented out (sends the exception to Raygun):
{
"timestamp": "2017-07-25T06:21:53.895Z"
"status": 200
"error": "OK"
"exception": "org.springframework.web.bind.MissingServletRequestParameterException"
"message": "Required String parameter 'foo' is not present"
"path": "/api/foo/bar/V1"
}
Any help or advice on what I'm doing wrong would be greatly appreciated. Thank you for your time.
In your controller advice try this way to map exception type to Http-Status as follows:
if (ex instanceof MyException)
{//just an example.
return new ResponseEntity<>(e, HttpStatus.BAD_REQUEST);
}
else
{//all other unhandled exceptions
return new ResponseEntity<>(e, HttpStatus.INTERNAL_SERVER_ERROR);
}
Here MyException is the type of exception you are throwing at runtime. Say I am handling Bad request.
I'm still unsure why it was returning a 200 OK status when an exception was being thrown. But I've realised that what I was doing with trying to create a string that mimics Spring's default json error message, was overly complex and not necessary at all.
Once I had sent the exception through to Raygun, I can just rethrow the exception and let the framework handle it like any exception annotated with #ResponseStatus.
#ExceptionHandler(value = Exception.class)
public void defaultErrorHandler(Exception e) throws Exception {
RaygunClient client = new RaygunClient("<MyRaygunAPIKey>");
// If the exception is annotated with #ResponseStatus rethrow it and let
// the framework handle it
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
// Otherwise send the exception Raygun and then rethrow it and let the framework handle it
if (accessToken.getUsername() != null && accessToken.getDatabaseName() != null) {
ArrayList tags = new ArrayList<String>();
tags.add("username: " + accessToken.getUsername());
tags.add("database: " + accessToken.getDatabaseName());
client.Send(e, tags);
accessToken = null;
throw e;
} else if (databaseName != null) {
ArrayList tags = new ArrayList<String>();
tags.add("database: " + databaseName);
client.Send(e, tags);
databaseName = null;
throw e;
} else {
client.Send(e);
throw e;
}
}
A bare bones implentation would look like this:
#ExceptionHandler(value = Exception.class)
public void defaultErrorHandler(Exception e) throws Exception {
RaygunClient client = new RaygunClient("<MyRaygunAPIKey>");
// If the exception is annotated with #ResponseStatus rethrow it and let the framework handle it
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
// Otherwise send the exception Raygun and then rethrow it and let the framework handle it
else {
client.Send(e);
throw e;
}
}