I got a function that make an update of an issue in jira and i want to throw the catch using JUnit.
This is the function that I got:
#PutMapping (value = "/update/{issueKey}")
public ResponseEntity<ResponseDTO>
updateIssue(#Validated #RequestBody EventDTO eventDTO, BindingResult result, #PathVariable String issueKey)
{
logger.info("Entra en /update con el payload: {}", eventDTO);
if (result.hasErrors())
{
ErrorResponseDTO errorResponseDTO = ErrorResponseDTO.getErrorResponseDTOFromBinding(result, messageSource);
return new ResponseEntity<>(errorResponseDTO, HttpStatus.BAD_REQUEST);
}
try
{
SuccessResponseDTO successResponseDTO = jiraService.update(eventDTO, issueKey);
logger.info("/update response {} ", successResponseDTO);
return new ResponseEntity<>(successResponseDTO, HttpStatus.OK);
}
catch (EywaException eywaException)
{
logger.error("Se ha producido un error en actualizar un issue", eywaException);
ErrorResponseDTO responseDTO = new ErrorResponseDTO();
String errorMessage = messageSource.getMessage(eywaException.getMessage(), null,
LocaleContextHolder.getLocale());
responseDTO.addErrorResponseDTO(eywaException.getMessage().split("\\.")[0], errorMessage);
return new ResponseEntity<>(responseDTO, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
This is what i have in the JUnit
#Test
public void updateIssue_NonExistanceIssue_shouldReturnFail() throws Exception
{
when(jiraService.update(eventDTO, "")).thenReturn(successResponseDTO);
String json = "{\"summary\":\""+eventDTO.getSummary()+"\""+
", \"description\":\""+eventDTO.getDescription()+"\""+
", \"raised\":\""+eventDTO.getRaised()+"\""+
", \"issueType\":\""+eventDTO.getIssueType()+"\""+
", \"priority\":\""+eventDTO.getPriority()+"\""+
", \"issuesubType\":\""+eventDTO.getIssuesubType()+"\""+
", \"region\":\""+eventDTO.getRegion()+"\""+
", \"airport\":\""+eventDTO.getAirport()+"\""+"}";
mvc.perform(put(BASE + UPDATE.replaceAll("\\{.*\\}", ""))
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isInternalServerError());
}
(I have already created the object in the setup)
The status i'm getting is the 405 and when i put an issueKey i got the status 200 (even when the issue key don't exist)
It has to throw status 500
I think that in the JUnit test you have to make .thenThrow(eywaException); instead of the .thenReturn(successResponseDTO); to make the test go through the exception and get the 500 status
I think your problem is the path variable.
The end point is "/update/{issueKey}" but during the test you call the "/update/" (?).
The Spring knows the first end-point, you call the second and spring cant find this end-point and throws 405 Method Not Allowed (You don't have PUT /update).
Related
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
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.
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;
}
}
Running Spring 4.2.6.RELEASE, we are experiencing unexpected behavior with fairly typical RestTemplate usage; a 401 error is not being translated to a HttpClientErrorException.
Specifically, when we receive an expected 401 from the server, we receive a ResourceAccessException wrapping an IOException with the message Server returned HTTP response code: 401 for URL: ..., which is raised deep within the bowels of sun.net.www.protocol.http.HttpURLConnection.
Our template is configured more or less like so:
public RestTemplate restTemplate() {
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
// connections per route is not a meaningful limit for us, so set very high
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(10000);
poolingHttpClientConnectionManager.setMaxTotal(100);
CloseableHttpClient client = HttpClientBuilder.create().setConnectionManager(poolingHttpClientConnectionManager).build();
HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(httpRequestFactory);
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
Our usage of the client looks like:
protected <T, U> ResponseEntity<T> request(UserClientAccount account, U body, String url, HttpMethod httpMethod,
ParameterizedTypeReference<T> responseType, boolean retry) {
try {
HttpEntity<U> request = createHttpEntity(body, account.getToken(this));
return getRestTemplate().exchange(url, httpMethod, request, responseType, new HashMap<String, String>());
}
catch (HttpClientErrorException e) {
if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) {
if (retry) {
log.warn("Unauthorized response for {}. Refreshing token to retry...", url);
refreshToken(account);
// tries again
return request(account, null, url, httpMethod, responseType, false);
}
else {
log.error("Unauthorized error calling {}. All attempts to retry exhausted ", url);
throw e;
}
}
throw new ProgramException("Error while performing " + httpMethod + " request to " + url + ". " +
"Response body: " + e.getResponseBodyAsString(), e);
}
}
Our catch of HttpClientErrorException is never hit; instead, we receive a ResourceAccessException with a cause of the above mentioned IOException.
What are we doing wrong?
Does anybody know why I cannot use #ResponseStatus(reason = "My message") on an exception handler in spring MVC while still returning a #ResponseBody. What seems to happen is that if I use the reason attribute
// this exception handle works, the result is a 404 and the http body is the json serialised
// {"message", "the message"}
#ExceptionHandler
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public Map<String, String> notFoundHandler(NotFoundException e){
return Collections.singletonMap("message", e.getMessage());
}
// this doesn't... the response is a 404 and the status line reads 'Really really not found'
// but the body is actually the standard Tomcat 404 page
#ExceptionHandler
#ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Really really not found")
public Map<String, String> reallyNotFoundHandler(ReallyNotFoundException e){
return Collections.singletonMap("message", e.getMessage());
}
The code for this example is over on github.
It seems that this is a direct result of the following code from AnnotationMethodHandlerExceptionResolver
private ModelAndView getModelAndView(Method handlerMethod, Object returnValue, ServletWebRequest webRequest)
throws Exception {
ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(handlerMethod, ResponseStatus.class);
if (responseStatusAnn != null) {
HttpStatus responseStatus = responseStatusAnn.value();
String reason = responseStatusAnn.reason();
if (!StringUtils.hasText(reason)) {
// this doesn't commit the response
webRequest.getResponse().setStatus(responseStatus.value());
}
else {
// this commits the response such that any more calls to write to the
// response are ignored
webRequest.getResponse().sendError(responseStatus.value(), reason);
}
}
/// snip
}
This has been reported to Springsource in SPR-8251:
For the record, since Spring 3.2, this got even worse because the AnnotationMethodHandlerExceptionResolver has been replaced by the ResponseStatusExceptionResolver and it does:
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) throws Exception {
int statusCode = responseStatus.value().value();
String reason = responseStatus.reason();
if (this.messageSource != null) {
reason = this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale());
}
if (!StringUtils.hasLength(reason)) {
response.sendError(statusCode);
}
else {
response.sendError(statusCode, reason);
}
return new ModelAndView();
}
This is worth a bug report. Moreover, the #ResponseStatus is documented with setStatus and is ill-designed. It should have been called #ResponseError.
I have created two issues for this finally: SPR-11192 and SPR-11193.
Almost a year has passed and my two issues are still open. I do not consider Spring WebMVC as a first-class REST framework which it isn't imho, WebMVC is for humas and not machines :-(