I have an issue when trying to test a class that represents a Rest Client. I'm using RestTemplate in Spring Boot.
This is the abstract RestClient class:
public abstract class RestClient {
...
public RestResponse sendPostRequest(URI baseUri, String resource, IRestRequest restRequest, ClassresponseClass)
throws ServerException, ClientException {
...
try {
RestTemplate restTemplate = new RestTemplate();
response = restTemplate.exchange(baseUri, HttpMethod.POST, getEntity(restRequest), responseClass);
result = response.getBody();
getLogger().debug("[{}] received", result);
return result;
} catch (HttpClientErrorException e) {
throw new ClientException(e.getCause());
} catch (HttpServerErrorException e) {
throw new ServerException(e.getCause());
} catch (Exception e) {
getLogger().error("Error with cause: {}.", e.getMessage());
}
...
}
}
This is the actual implementation:
public class ActualRestClient extends RestClient {
public RestResponse sendFetchFileRequest(URI baseUri, FetchFileRequest request) throws ServerException, ClientException {
return sendPostRequest(baseUri, "FETCH_FILE", request, RestResponse.class);
}
}
An this is the test:
#RunWith(PowerMockRunner.class)
#PrepareForTest({ActualRestClient.class, RestClient.class})
public class ActualResRestClientTest {
private static final String REQUEST_URI = "something";
#InjectMocks
public ActualRestClient testee;
#Mock
private RestTemplate restTemplate;
#Test(expected = ServerException.class)
public void sendPostRequestWithResponseBody_throwsServerException() throws Exception {
HttpServerErrorException httpServerErrorException = new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR);
when(restTemplate.exchange(Mockito.any(URI.class), eq(HttpMethod.POST), Mockito.any(), eq(FetchFileRequest.class))).thenThrow(httpServerErrorException);
testee.sendFetchFileRequest(new URI(REQUEST_URI), new FetchFileRequest());
}
}
ClientException and ServerException are exceptions created by me by extending Exception class.
My problem is that in the RestClient class another Exception is catched(message:"URI is not absolute") instead of HttpServerErrorException and I can't understand why. Thank you!
As the commenter already expressed: doing new URI("something") already throws at you. But even when you pass a "valid" URI, your code will not work, as there is a misconception on your end. You see:
RestTemplate restTemplate = new RestTemplate();
response = restTemplate.exchange(baseUri, HttpMethod.POST, getEntity(restRequest), responseClass);
That code lives within a method of your class under test. But #InjectMocks works only for fields of classes.
In other words: when your production code gets executed, a new (completely different** ResponseTemplate instance is created. And therefore your mocking spec is irrelevant, because the method isn't invoked on your mock in the first place.
Two choices:
turn that local variable into a field of your class under test (then injecting should work)
or, as you are already using PowerMock(ito), you could use that mocking framework to intercept that call to new().
I suggest you rather use option one, and avoid to use the PowerMock(ito) extension altogether!
Related
I'm new to Spring Boot. I'm trying to handle a exception when a value is null. Basically, its a value that I'm suppose to get from another external service (using restTemplate.exchange() method) which is currently down, so a null value gets assigned to that variable. Here's the code for the same service:
ResponseEntity<AuthenticationResponse> respNode = null;
try {
respNode = restTemplate.exchange(url, HttpMethod.POST, httpEntity, new ParameterizedTypeReference<AuthenticationResponse>() {});
} catch (Exception e) {
if (respNode == null) {
log.info("Unable to reach: {} ", url);
throw new AuthServerException("Auth Server Not Found");
}
}
I created a custom Exception to handle the scenario if the value is null. The Exception is thrown and I'm able to read it in the logs, but I'm not able to handle it.
AuthServerException.java
package com.nokia.sp.module.myVerify.portalapp.exception;
public class AuthServerException extends NullPointerException {
private static final long serialVersionUID = 1L;
public AuthServerException(String message) {
super(message);
}
}
Controller:
#Controller
#RequiredArgsConstructor
public class LandingPageController {
#ExceptionHandler(AuthServerException.class)
public ModelAndView AuthServerExceptionHandler(AuthServerException e) {
log.info(e.getMessage());
ModelAndView mav = new ModelAndView();
mav.setViewName("auth-server-handler");
return mav;
}
}
Am I missing something or have I made any syntax Error? Please do help me with the same. Thank you!
Right now i'm using this example of exception handling:
//get an object of type curse by id
//in the service file, this findCurseById() method throws a
//CursaNotFoundException
#GetMapping("/{id}")
public ResponseEntity<curse> getCursaById (#PathVariable("id") Long id) {
curse c = curseService.findCurseById(id);
return new ResponseEntity<>(c, HttpStatus.OK);
}
//so if not found, this will return the message of the error
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(CursaNotFoundException.class)
public String noCursaFound(CursaNotFoundException ex) {
return ex.getMessage();
}
and that's my exception
public class CursaNotFoundException extends RuntimeException {
public CursaNotFoundException(String s) {
super(s);
}
}
in future I want to use Angular as front-end, so I don't really know how I should treat the exceptions in the back-end. For this example let's say, should I redirect the page to a template.html page in the noCursaFound() method, or should I return something else? A json or something? I couldn't find anything helpful. Thanks
I would suggest keeping the error handling at the REST API level and not redirecting to another HTML page on the server side. Angular client application consumes the API response and redirects to template.html if needed.
Also, it would be better if the backend returns an ApiError when an exception occurs with a message and, optionally, an error code:
public class ApiError {
private String message;
private String code;
}
and handle the exceptions in a separate class, ExceptionHandler annotated with #ControllerAdvice:
#ControllerAdvice
public class ExceptionHandler {
#ExceptionHandler(value = CursaNotFoundException.class)
public ResponseEntity cursaNotFoundException(CursaNotFoundException cursaNotFoundException) {
ApiError error = new ApiError();
error.setMessase(cursaNotFoundException.getMessage());
error.setCode(cursaNotFoundException.getCode());
return new ResponseEntity(error, HttpStatus.NOT_FOUND);
}
#ExceptionHandler(value = Exception.class)
public ResponseEntity<> genericException(Exception exception) {
ApiError error = new ApiError();
error.setMessase(exception.getMessage());
error.setCode("GENERIC_ERROR");
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
I am new to JUnit and Mockito framework. Here I am trying to mock the rest template and wanted to return an HTTP Status of 200, but still, it returns a null value. Can someone tell me whats is wrong with my implementation and why is it returning a null value, not HttpStatus OK .
Class MyDependencies{
#Autowired
RestTemplate template;
}
Class ABC extends MyDependencies{
void verify(){
try{
ResponseEntity<Object> response;
try{
response= template.postForEntity("localhost:....",obj,obj);
}catch(Exception e){
throws Exception.......
}
if(response.getStatusCodeValue()==200) // When reaches here, Exception is thrown
// becoz response is null
return new ResponseEntity<>(HttpStatus.OK);
else
throw Custom_Exception.....
}catch(Exception e){
throws Exception.....
}
}
}
Testing
Class MyTesting{
#InjectMocks
ABC abc;
#Mock
RestTemplate template;
#BeforeEach
void setUp(){
abc=new ABC();
MockitoAnnotations.initMocks(this);
}
#Test
void testingIt(){
when(template.postForEntity(anyString(),any(),any())).thenReturn(new ResponseEntity<>(HttpStatus.OK));
Assertions.asserDoesNotThrow(()->abc.verify());
}
}
The issue is the matchers which we are supplying from test method are not matching the actual function call in the service class, hence the stub is not executed and the response is null.
Try specifying the when stub like this
when(restTemplate.postForEntity(anyString(), any(), Mockito.<Class<String>>any()))
.thenReturn(new ResponseEntity<String>("{\"status\" : \"ok\"}", HttpStatus.OK));
In this case, I have taken the String as an entity I am posting through rest template. You may use some other class here.
Service class call for this will look like this
ResponseEntity<String> answer = template.postForEntity("/abc/1", entity, String.class);
Also, you have #InjectMocks on your service class in your test, so you don't need this line
abc = new ABC();
MockitoAnnotations.initMocks(this) is already doing that for you.
#code_mechanic is right, issue is with the matchers supplied from the test method.
Rather than below in test method:
when(template.postForEntity(anyString(),any(),any())).thenReturn(new ResponseEntity<>(HttpStatus.OK));
Try this, provide url not anyString() :
when(template.postForEntity(ArgumentMatchers.endsWith("/abc/1"),any(),any())).thenReturn(new ResponseEntity<>(HttpStatus.OK));
This way it will figure out URL matching stub, it worked for me.
I'm using RestTemplate to call my webservice's health actuator endpoint from another webservice of mine to see if the webservice is up. If the webservice is up, all works fine, but when it's down, I get an error 500, "Internal Server Error". If my webservice is down, I'm trying to catch that error to be able to handle it, but the problem I'm having is that I can't seem to be able to catch the error.
I've tried the following and it never enters either of my catch sections
#Service
public class DepositService {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofMillis(3000))
.setReadTimeout(Duration.ofMillis(3000))
.build();
}
private static void getBankAccountConnectorHealth() {
final String uri = "http://localhost:9996/health";
RestTemplate restTemplate = new RestTemplate();
String result = null;
try {
result = restTemplate.getForObject(uri, String.class);
} catch (HttpClientErrorException exception) {
System.out.println("callToRestService Error :" + exception.getResponseBodyAsString());
} catch (HttpStatusCodeException exception) {
System.out.println( "callToRestService Error :" + exception.getResponseBodyAsString());
}
System.out.println(result);
}
}
I've also tried doing it this way, but same results. It never seems to enter my error handler class.
public class NotFoundException extends RuntimeException {
}
public class RestTemplateResponseErrorHandler implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse httpResponse) throws IOException {
return (httpResponse.getStatusCode().series() == CLIENT_ERROR || httpResponse.getStatusCode().series() == SERVER_ERROR);
}
#Override
public void handleError(ClientHttpResponse httpResponse) throws IOException {
if (httpResponse.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR) {
// handle SERVER_ERROR
System.out.println("Server error!");
} else if (httpResponse.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR) {
// handle CLIENT_ERROR
System.out.println("Client error!");
if (httpResponse.getStatusCode() == HttpStatus.NOT_FOUND) {
throw new NotFoundException();
}
}
}
}
#Service
public class DepositService {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofMillis(3000))
.setReadTimeout(Duration.ofMillis(3000))
.build();
}
private static void getAccountHealth() {
final String uri = "http://localhost:9996/health";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new RestTemplateResponseErrorHandler());
String result = null;
result = restTemplate.getForObject(uri, String.class);
System.out.println(result);
}
}
Any suggestions as to how I can call my webservice's health actuator from another webservice and catch if that webservice is down?
It looks like the getForObject doesn't throw either of the exceptions you are catching. From the documentation, it throws RestClientException. The best method I have found for identifying thrown exceptions is to catch Exception in the debugger and inspect it to find out if it's useful.
With the second method, I'm not sure why you would create a bean method for the RestTemplate and then create one with new. You probably should inject your RestTemplate and initialise the ResponseErrorHandler with the RestTemplateBuilder::errorHandler method.
Internal serve error throw HttpServerErrorException You should catch this exception if you want to handle it However the better way to do that is using error handler you can see the following posts to see how to do that:
spring-rest-template-error-handling
spring-boot-resttemplate-error-handling
I'm trying to create a test where I have to mock a method inside the class that I want to test. But it keeps calling the real method, but I want mock it.
The method that I want to mock is
extractSecretValue(String path)
I know it's not mocking the method because there is a "println", and it's printing.
What am I doing wrong?
I'm using JUnit 5
The class that I want to test:
#Configuration
public class RestTemplateConfig {
#Value("${******}")
private String keystore;
#Value("${******}")
private String identificador;
#Value("${******}")
private String token;
#Bean
public RestTemplate restTemplate() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext context = null;
context = SSLContext.getInstance("TLSv1.2");
context.init(null, null, null);
List<Header> headers = new ArrayList<>();
headers.add(new BasicHeader("Authorization", "Bearer " + extractSecretValue(token)));
CloseableHttpClient httpClient = HttpClientBuilder.create().setSSLContext(context).setDefaultHeaders(headers)
.build();
HttpComponentsClientHttpRequestFactory hcchr = new HttpComponentsClientHttpRequestFactory(httpClient);
hcchr.setConnectionRequestTimeout(10000);
return new RestTemplate(hcchr);
}
public String extractSecretValue(String path) {
System.out.println("Test1");
Path secretPath = Paths.get(path);
String value = "";
try (Stream<String> lines = Files.lines(secretPath)) {
value = lines.collect(Collectors.joining());
} catch (IOException ignored) {
throw new ApplicationException(ignored);
}
return value.isEmpty() ? path : value;
}
}
The Test class:
#ExtendWith(MockitoExtension.class)
public class RestTemplateConfigTest {
#Test
public void return_restTemplateConfig() {
RestTemplateConfig restTemplateConfig = new RestTemplateConfig();
RestTemplateConfig restTemplateMock;
RestTemplate restTemplate;
restTemplateMock = Mockito.spy(restTemplateConfig);
try {
when(restTemplateMock.extractSecretValue(anyString())).thenReturn("423424");
restTemplate = restTemplateMock.restTemplate();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new ApplicationException(e);
}
}
}
I've already tried this too:
doReturn("2332").when(restTemplateMock).extractSecretValue(anyString());
If you use when(...).thenReturn(...) the real method will still be invoked
(from Mockito, which is not relevant for your test),
but that should not happen when you use the doReturn(...).when(...) notation instead.
The problem in your test is that token is null and your anyString() does not match that as it only matches non-null strings.
Use any() instead, which matches anything, including nulls.
Combine that with the doReturn(...).when(...) and your test should succeed.
If you do not want the actual methods to be called, then you should be using Mockito.mock() and not Mockito.spy().
you should update your test class to use :
restTemplateMock = Mockito.mock(RestTemplateConfig.class);
You haven't mocked your RestTemplateConfig, you've instantiated it.
What you want is:
restTemplateMock = Mockito.spy(new RestTemplateConfig());