How to assert the correct exception with TestRestTemplate? - java

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);

Related

Test ExceptionHandler with RestTemplate

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.

How to document #ControllerAdvice handled exception using Spring REST Docs

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.

HttpClientErrorException 400 null using RestTemplate in microServices

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 {
}

Spring Boot Returning 401 Statused Custom Object From Exception Handler

I have an Spring Boot rest API that have an LoginController for doing simple authentication processes. This is the token validation action.
#PostMapping("validatetoken")
public ResponseEntity<ValidateTokenResponse> validateToken(
#RequestBody ValidateTokenRequest validateTokenRequest, HttpServletRequest request) throws Exception {
if (StringUtils.isEmpty(validateTokenRequest.getToken())) throw new AuthenticationFailedException("token parameter cannot be null or empty");
Boolean isValid = authenticationService.validateToken(request, validateTokenRequest.getToken());
ValidateTokenResponse response = new ValidateTokenResponse();
response.setIsValid(isValid);
response.setToken(validateTokenRequest.getToken());
return new ResponseEntity<>(response, isValid? HttpStatus.OK : HttpStatus.UNAUTHORIZED);
}
And in my api I'm catching all errors in a ResponseEntityExceptionHandler and converting a custom object this way.
#ExceptionHandler(AuthenticationFailedException.class)
#ResponseBody
protected ResponseEntity<Object> handleAuthenticationFailed(AuthenticationFailedException ex) {
LogManager.error(ex);
ApiError apiError = new ApiError(HttpStatus.UNAUTHORIZED);
apiError.setMessage(ex.getMessage());
return buildResponseEntity(apiError);
}
But when I want to call this api using RestTemplate like below I'm getting and exception like java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode.
ResponseEntity<String> responseEntity =
restTemplate.exchange(
this.validateTokenUrl,
HttpMethod.POST,
requestHttpEntity,
String.class);
But if I change HttpStatus from ExceptionHandler to except HttpStatus.UNAUTHORIZED I can get true ApiError object from the client. What can cause this problem and how can I resolve it?
EDIT: Created a github repo that mimics my problemic parts of my project.
To make it clear, I suggest to autowire resttemplate in separate config file with below detail
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new ErrorHandler());
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setOutputStreaming(false);
restTemplate.setRequestFactory(requestFactory);
return restTemplate;
}
By default RestTemplate uses DefaultResponseErrorHandler. This error handler throws exception whenever 4xx/5xx response is send from REST API.
If you want custom error handling, just register your custom error handler via restTemplate.setErrorHandler, where you would use your implementation of ResponseErrorHandler interface.
This is a default behavor for SimpleClientHttpRequestFactory (uses the JDK's internal http client)
A simple fix is to use apache http components library:
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
restTemplate.setRequestFactory(requestFactory);
see this link for more details: https://github.com/spring-projects/spring-security-oauth/issues/441#issuecomment-92033542

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