i've implemented a Interceptors for RestTemplate (RestTemplateInterceptor.class):
#Autowired
private RestConfigurations configurations;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.additionalInterceptors((ClientHttpRequestInterceptor) (httpRequest, bytes, clientHttpRequestExecution) -> {
HttpHeaders headers = httpRequest.getHeaders();
headers.add("X-API-KEY", configurations.getApiKey());
return clientHttpRequestExecution.execute(httpRequest,bytes);
}).build();
}
Now I want to test it. :)
One try looks like that:
#ExtendWith(SpringExtension.class)
#RestClientTest({RestTemplateInterceptor.class, RestConfigurations.class})
#AutoConfigureMockMvc
#SpringBootTest
class MyTestClass {
public static final String URL = "http://testMe.com/";
#Autowired
MockRestServiceServer mockServer;
#Autowired
RestTemplateBuilder restTemplateBuilder;
#Test
#DisplayName("Should include header X-API-KEY")
void headerShouldContainsXApiKey() throws Exception {
mockServer.expect(requestTo(URL)).andRespond(withSuccess("Hello world", TEXT_PLAIN));
String body = restTemplateBuilder.build().exchange(URL//
, GET, //
null, //
String.class).getBody();
assertThat(body).isEqualTo("Hello world");
mockServer.expect(header("X-API-KEY", ""));
}
But i failed with:
java.lang.AssertionError: No further requests expected: HTTP GET http://test.hornbach.de/
0 request(s) executed.
Anyone a hint? Thanks
If you check the Javadoc, you'll see that when you call additionalInterceptors, you're not modifying the existing builder instance but instead getting a new builder with a slightly different configuration. When you then call restTemplateBuilder.build() in your test case, you're building a template that has the unmodified configuration.
The ideal way to test something like this is to do so directly on the interceptor instance itself, but if you're wanting to perform a functional test by calling through to a mock server, you should inject the completed RestTemplate instance into your test case.
Related
Here a test code sample with Spring Boot Test:
#SpringBootTest
#AutoConfigureMockMvc
class SendMoneyControllerSpringBootSpyBeanTest {
#Autowired
private MockMvc mockMvc;
#SpyBean
private SendMoneyUseCase sendMoneyUseCase;
#Test
void testSendMoneyWithMock() {
...
}
#Test
void testSendMoneyWithSpy() {
...
}
}
Now suppose the two test methods like in the snippet above. One is using the spy version of the spring bean, whereas the other is using a mock version of the spring bean. How can I mix up both and distinguish them in my test methods ?
For example, can I do :
#SpringBootTest
#AutoConfigureMockMvc
class SendMoneyControllerSpringBootSpyBeanTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private SendMoneyUseCase sendMoneyUseCaseMocked;
#SpyBean
private SendMoneyUseCase sendMoneyUseCaseSpied;
}
I know that in spring bean container, there is only one of sendMoneyUseCaseMocked or sendMoneyUseCaseSpied because they are the same java type. I can use a qualifier, to refer them by name. But in that case, how do I write my mock condition in the corresponding test method (write either mock condition on the mocked bean or the spied condition on the spied bean in the concerned test method).
EDIT : Another approach is to remove the line of code #MockBean, like this, the spied method is working. Consequently, I need then to programmatically code #MockBean in the mocked test method with Spring boot API, but how to do ?.
Thx.
I see two solutions.
Either use two test classes. One with the #MockBean object and one with the #SpyBean object. Both can inherit from the same abstract parent-class.
Let Spring Boot inject #MockBean and then replace it manually in the testSendMoneyWithSpy() test with the #SpyBean object using reflection.
But, why do you want to use the spy-object at all?
If you make unit-tests, you should mock the service and only test the controller here. And test the SendMoneyUseCase class in the SendMondeyUseCaseTest test.
(btw. strange name SendMondeyUseCase why not SendMoneyService or SendMoneyComponent. Or MoneySendService?)
Here the solution, first the solution code (my real test case, the one in first topic is more like a study code) :
#Slf4j
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class DocumentsApiTest extends ProductAPITestRoot {
#Autowired
private TestRestTemplate restTemplate;
#SpyBean
private AlfrescoService alfrescoServiceSpy;
#MockBean
private CloseableHttpClient closeableHttpClient;
#Test
public void testUploadDocument_SUCCESS() {
HttpHeaders headers = createOKHttpHeaders();
DocumentWithFile documentWithFile = createDocumentWithFile();
doReturn(StringUtils.EMPTY).when(alfrescoServiceSpy).sendDocument(anyString(), any(InputStream.class), eq(documentWithFile.getNomFichier()));
HttpEntity<DocumentWithFile> request = new HttpEntity<>(documentWithFile, headers);
ResponseEntity<Long> response = restTemplate.exchange(
"/documents/upload", HttpMethod.POST, request, Long.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isEqualTo(1L);
}
#Test
public void testUploadDocument_500_INTERNAL_SERVER_ERROR() {
HttpHeaders headers = createOKHttpHeaders();
DocumentWithFile documentWithFile = createDocumentWithFile();
doThrow(new AlfrescoServiceException("Alfresco has failed !")).when(alfrescoServiceSpy).sendDocument(anyString(), any(InputStream.class), eq(documentWithFile.getNomFichier()));
HttpEntity<DocumentWithFile> request = new HttpEntity<>(documentWithFile, headers);
ResponseEntity<String> response = restTemplate.exchange(
"/documents/upload", HttpMethod.POST, request, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
assertThat(response.getBody()).contains("Internal Server Error");
}
#Test
public void testUploadDocument_502_BAD_GATEWAY() throws IOException {
HttpHeaders headers = createOKHttpHeaders();
DocumentWithFile documentWithFile = createDocumentWithFile();
CloseableHttpResponse alfrescoResponse = mock(CloseableHttpResponse.class);
when(closeableHttpClient.execute(any(HttpPost.class))).thenReturn(alfrescoResponse);
when(alfrescoResponse.getStatusLine()).thenReturn(new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.BAD_GATEWAY.value(), "FINE!"));
HttpEntity<DocumentWithFile> request = new HttpEntity<>(documentWithFile, headers);
ResponseEntity<String> response = restTemplate.exchange(
"/documents/upload", HttpMethod.POST, request, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_GATEWAY);
assertThat(response.getBody()).contains("Erreur Alfresco");
}
private static HttpHeaders createOKHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + OK_TOKEN);
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
private static DocumentWithFile createDocumentWithFile() {
String fileAsString = RandomStringUtils.randomAlphabetic((int) 1e6);
byte[] fileAsStringBase64 = Base64.getEncoder().encode(fileAsString.getBytes());
DocumentWithFile documentWithFile = new DocumentWithFile();
String nomFichierExpected = "nomFichier.pdf";
documentWithFile
.id(8L)
.idCatalogue("idCatalogue")
.nom("nom")
.reference("reference")
.version("version")
.type(TypeDocument.IPID)
.fichier(new String(fileAsStringBase64))
.nomFichier(nomFichierExpected);
return documentWithFile;
}
}
If you look carrefully, the right way to mixup both : a mock and a spy of the same bean.
You can use #MockBean....and you use the spy version of this #MockBean with when(...).thenCallRealMethod(). But the REAL drawback of this is that if the #MockBean bean contains #Value field injection, thus they are NOT initialized. Meaning that #MockBean annotation set #Value fields of the mocked bean to null.
So I went for solution 2) because I need injection of #Value fileds.
Instead of #MockBean use #SpyBean of the concerned spring bean. Like this, you've got now the real bean. The question is how do I use it like a #MockBean. So to use a #SpyBean like a #MockBean, you needs to force the returned value of your #SpyBean bean like this for example :
doReturn(StringUtils.EMPTY).when(alfrescoServiceSpy).sendDocument(anyString(), any(InputStream.class), eq(documentWithFile.getNomFichier()));
As you can see, although alfrescoServiceSpy call the real code (not a mock), then you still can change its default behavior (calling the real code) with the mocked behavior like the snipped code above as example.
So test methods that need the mock version of #SpyBean declare an instruction of mocked behavior to do. And test methods that needs real code they don't do anything and #Value will be injected into the bean annotated #SpyBean the right way.
I'am personnaly satisfied of this code version.
Thanks you very much all.
I want to call another web-api from my backend on a specific request of user. For example, I want to call Google FCM send message api to send a message to a specific user on an event.
Does Retrofit have any method to achieve this? If not, how I can do that?
This website has some nice examples for using spring's RestTemplate.
Here is a code example of how it can work to get a simple object:
private static void getEmployees()
{
final String uri = "http://localhost:8080/springrestexample/employees.xml";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(uri, String.class);
System.out.println(result);
}
Modern Spring 5+ answer using WebClient instead of RestTemplate.
Configure WebClient for a specific web-service or resource as a bean (additional properties can be configured).
#Bean
public WebClient localApiClient() {
return WebClient.create("http://localhost:8080/api/v3");
}
Inject and use the bean from your service(s).
#Service
public class UserService {
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(3);
private final WebClient localApiClient;
#Autowired
public UserService(WebClient localApiClient) {
this.localApiClient = localApiClient;
}
public User getUser(long id) {
return localApiClient
.get()
.uri("/users/" + id)
.retrieve()
.bodyToMono(User.class)
.block(REQUEST_TIMEOUT);
}
}
Instead of String you are trying to get custom POJO object details as output by calling another API/URI, try the this solution. I hope it will be clear and helpful for how to use RestTemplate also,
In Spring Boot, first we need to create Bean for RestTemplate under the #Configuration annotated class. You can even write a separate class and annotate with #Configuration like below.
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
Then, you have to define RestTemplate with #Autowired or #Injected under your service/Controller, whereever you are trying to use RestTemplate. Use the below code,
#Autowired
private RestTemplate restTemplate;
Now, will see the part of how to call another api from my application using above created RestTemplate. For this we can use multiple methods like execute(), getForEntity(), getForObject() and etc. Here I am placing the code with example of execute(). I have even tried other two, I faced problem of converting returned LinkedHashMap into expected POJO object. The below, execute() method solved my problem.
ResponseEntity<List<POJO>> responseEntity = restTemplate.exchange(
URL,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<POJO>>() {
});
List<POJO> pojoObjList = responseEntity.getBody();
Happy Coding :)
Create Bean for Rest Template to auto wiring the Rest Template object.
#SpringBootApplication
public class ChatAppApplication {
#Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ChatAppApplication.class, args);
}
}
Consume the GET/POST API by using RestTemplate - exchange() method. Below is for the post api which is defined in the controller.
#RequestMapping(value = "/postdata",method = RequestMethod.POST)
public String PostData(){
return "{\n" +
" \"value\":\"4\",\n" +
" \"name\":\"David\"\n" +
"}";
}
#RequestMapping(value = "/post")
public String getPostResponse(){
HttpHeaders headers=new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
HttpEntity<String> entity=new HttpEntity<String>(headers);
return restTemplate.exchange("http://localhost:8080/postdata",HttpMethod.POST,entity,String.class).getBody();
}
Refer this tutorial[1]
[1] https://www.tutorialspoint.com/spring_boot/spring_boot_rest_template.htm
As has been mentioned in the various answers here, WebClient is now the recommended route.
You can start by configuring a WebClient builder:
#Bean
public WebClient.Builder getWebClientBuilder(){
return WebClient.builder();
}
Then inject the bean and you can consume an API as follows:
#Autowired
private WebClient.Builder webClientBuilder;
Product product = webClientBuilder.build()
.get()
.uri("http://localhost:8080/api/products")
.retrieve()
.bodyToMono(Product.class)
.block();
Does Retrofit have any method to achieve this? If not, how I can do that?
YES
Retrofit is type-safe REST client for Android and Java. Retrofit turns your HTTP API into a Java interface.
For more information refer the following link
https://howtodoinjava.com/retrofit2/retrofit2-beginner-tutorial
In this case need download whit my API, files hosted in other server.
In my case, don't need use a HTTP client to download the file in a external URL, I combined several answers and methods worked in previous code for files that were in my local server.
My code is:
#GetMapping(value = "/download/file/pdf/", produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<Resource> downloadFilePdf() throws IOException {
String url = "http://www.orimi.com/pdf-test.pdf";
RestTemplate restTemplate = new RestTemplate();
byte[] byteContent = restTemplate.getForObject(url, String.class).getBytes(StandardCharsets.ISO_8859_1);
InputStream resourceInputStream = new ByteArrayInputStream(byteContent);
return ResponseEntity.ok()
.header("Content-disposition", "attachment; filename=" + "pdf-with-my-API_pdf-test.pdf")
.contentType(MediaType.parseMediaType("application/pdf;"))
.contentLength(byteContent.length)
.body(new InputStreamResource(resourceInputStream));
}
and it works with HTTP and HTTPS urls!
Since the question explicitly tags spring-boot, it worth noting that recent versions already ship a pre-configured instance of a builder for WebClient, thus you can directly inject it inside your service constructor without the needing to define a custom bean.
#Service
public class ClientService {
private final WebClient webClient;
public ClientService(WebClient.Builder webClientBuilder) {
webClient = webClientBuilder
.baseUrl("https://your.api.com")
}
//Add all the API call methods you need leveraging webClient instance
}
https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/boot-features-webclient.html
Simplest way I have found is to:
Create an annotated interface (or have it generated from somehing like OpenAPI)
Give that interface to Spring RestTemplate Client
The Spring RestTemplate Client will parse the annotations on the interface and give you a type safe client, a proxy-instance. Any invocation on the methods will be seamlessly translated to rest-calls.
final MyApiInterface myClient = SpringRestTemplateClientBuilder
.create(MyApiInterface.class)
.setUrl(this.getMockUrl())
.setRestTemplate(restTemplate) // Optional
.setHeader("header-name", "the value") // Optional
.setHeaders(HttpHeaders) // Optional
.build();
And a rest call is made by inoking methods, like:
final ResponseEntity<MyDTO> response = myClient.getMyDto();
I implemented resilience4j in my project using the Spring Boot2 starter (https://resilience4j.readme.io/docs/getting-started-3).
I annotated a method with #CircuitBreaker that uses http client for calling an external service and the circuit breaker is working fine - including its fallback.
I'd like to add unit tests for it but when I run a test trying to simulate the fallback, nothing happens - the exception is thrown but is not handled by the circuit breaker mechanism.
I've found some examples using its metrics but it is not useful in my case.
Any thoughts?
Here is a snippet of my client:
#CircuitBreaker(name = "MY_CICUIT_BREAKER", fallbackMethod = "fallback")
public ResponseEntity<String> search(String value) {
ResponseEntity<String> responseEntity = restTemplate.exchange(
searchURL,
HttpMethod.GET,
new HttpEntity(new HttpHeaders()),
String.class,
value);
}
public ResponseEntity<String> fallback(String value, ResourceAccessException ex) {
return "fallback executed";
}
As andres and pvpkiran mentioned/explained, I had to add a integration test.
You can achieve that basically adding #SpringBootTest annotation to your test class, it will bootstrap a container with spring context on it.
I also autowired CircuitBreakerRegistry in order to reset the circuit breaker before each test so I could guarantee a clean test. For mocking/spying/verifying I used Mockito from spring boot test starter (spring-boot-starter-test).
Here is how I managed to test the fallbacks methods:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = Application.class)
public class RestClientIntegrationTest {
private final String SEARCH_VALUE = "1234567890";
#MockBean( name = "myRealRestTemplateName")
private RestTemplate restTemplate;
#SpyBean
private MyRestClient client;
#Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;
#BeforeEach
public void setUp() {
circuitBreakerRegistry.circuitBreaker("MY_CIRCUIT_BREAKER_NAME").reset();
}
#Test
public void should_search_and_fallback_when_ResourceAccessException_is_thrown() {
// prepare
when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class), eq(SEARCH_VALUE)))
.thenThrow(ResourceAccessException.class);
String expectedResult = "expected result when fallback is called";
// action
String actualResult = client.search(SEARCH_VALUE);
// assertion
verify(client).fallback(eq(SEARCH_VALUE), any(ResourceAccessException.class));
assertThat(actualResult, is(expectedResult));
}
}
I hope there is no compilation error since I had to remove some non-relevant stuff.
You shouldn't test #CircuitBreaker in a unit test as it involves more than one class. Rather use an integration test.
I have a problem to mock restTemplate with Mockito
code want to be test:
public class Feature{
public static String getfeature(String url){
RestTemplate restTemplate = new RestTemplate();
String xml = "\"feature\": 1";
String json = restTemplate.postForObject(url, xml, String.class);
return json;
}
}
junit code:
#Mock
RestTemplate restTemplate=mock(RestTemplate.class);
#Test
public void testGetfeature(){
string testResponse= "\"feature\": 1";
Mockito.when((String)restTemplate.postForObject(
Mockito.any(String.class),
Mockito.any(Map.class),
Mockito.any(Class.class)
)).thenReturn(testResponse);
Feature feature = new Feature();
feature.getfeature("http://mockValue");
}
I set breaking point at feature.getfeature("http://mockValue"). It still try to connect to the remote server. I don't want postForObject to connect to the http://mockValue.
How should I mock the restTemplate to make postForObject not to connect to http://mockValue?
You are creating a new RestTemplate object in getfeature() method. So, mocking RestTemplate has no effect. Either take RestTemplate as an argument in getfeature() method or take it as constructor argument in Feature class.
Then from the test class, you can mock RestTemplate and pass it like below:
Feature feature= new Feature(mockRestTemplate);
feature.getfeature(url);
Or
Feature feature = new Feature();
feature.getfeature(mockRestTemplate, url);
You have to make the necessary changes in the Feature class based on the decision.
Here is the running code sample:
Main class:
public class Feature {
public static String getFeature(String url, RestTemplate restTemplate) {
return restTemplate.postForObject(url, "", String.class);
}
}
Test class:
Notice the way the RestTemplate is mocked and then the response is mocked.
public class FeatureTest {
#Test
public void testFeature() {
RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
Mockito.when(restTemplate.postForObject(Mockito.any(String.class),
Mockito.any(Object.class), Mockito.any(Class.class))).thenReturn("abc");
System.out.println(Feature.getFeature("http://abc", restTemplate));
}
}
The running code sample is also available at github
Feature.java and FeatureTest.java
There is no need to constantly create a RestTemplate object.
You can create one and inject it into the class.
Then, in your unit test, create a mock RestTemplate and inject that.
Edit:
Concerning the static method.
Don't use a static method,
that is terrible.
If you are using Spring (or any other Dependency Injection framework)
just inject an instance of Feature where ever you want to make the rest call.
Also,
in the Spring world Feature is a service class.
Use the #Service annotation and make the method not static.
I think you need to change your code to make your unit test work atleast using Mockito or you have to use some other library like powermock to mock local object instantiation.
1) create a constructor that accepts RestTemplate as argument to inject your mock.
or
2) create some setter method to inject that RestTemplate.
one more way is to create another method where RestTemplate can be passed.
public String getStringAsJson(RestTemplate restTemplate, String url, String xml) {
return restTemplate.postForObject(url, xml, String.class);
}
then in your test:
RestTemplate mockRestTemplate = mock(RestTemplate.class);
when(restTemplate.postForObject(mockurl, mockUrl, mockXml)).thenReturn("Json");
feature.getStringAsJson(mockRestTemplate,mockUrl,mockXml);
how to junit test on restTemplate?
We test what it returns.
Currently your implementation does nothing itself, it simply delegates to a RestTemplate and return its result.
The fiveelements answer describes the implementation (with broad accepted values as parameters) :
#Test
public void testFeature() {
RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
Mockito.when(restTemplate.postForObject(Mockito.any(String.class),
Mockito.any(Object.class), Mockito.any(Class.class))).thenReturn("abc");
System.out.println(Feature.getFeature("http://abc", restTemplate));
}
That doesn't assert the actual behavior : the URL could be erroneous, the body posted or the response could be wrong and so for... and this test will never detect that. Finally, very important things could be wrong in this implementation and we could not detect that. The value of this kind of test is so weak.
Since this method doesn't perform itself logic, this is better suited for an integration test that could assert and catch much more thing/issues in the actual behavior :
#Test
public void testFeature() {
String actualFeature = Feature.getFeature("http://...");
String expectedFeature = ...;
Assertions.assertEquals(expectedFeature, actualFeature);
}
If you use PowerMockito then you can do like following:
No code change needed here for Feature class then you can use this directly if PowerMockito lib available in prj
RestTemplate mockedRestTemplate = PowerMockito.mock(RestTemplate.class);
PowerMockito.whenNew(RestTemplate.class).withAnyArguments().thenReturn(mockedRestTemplate);
I have an interface which has a method without arguments
public interface HealthStatus {
Integer healthCheck();
}�
The implementation is as below
#Override
public Integer healthCheck() {
Integer status = 0;
try {
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
if (proxyConfigProperties.getEnabled()) {
Proxy proxy = new Proxy(java.net.Proxy.Type.HTTP, new InetSocketAddress(proxyConfigProperties.getUrl(), proxyConfigProperties.getPort()));
simpleClientHttpRequestFactory.setProxy(proxy);
}
RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(simpleClientHttpRequestFactory));
ResponseEntity<String> healthResponse = restTemplate.exchange(eVerifyGovernmentProperties.getUrl(), HttpMethod.GET, null, String.class);
status = (healthResponse.getStatusCode() == HttpStatus.OK) ? 200 : 202;
} catch (Throwable th) {
th.printStackTrace();
}
return status;
}�
How can I unit test this method for positive scenarios and negative scenarios.
Edit:
I have refactored my class as below
#Service
#Qualifier("implementation")
public class HealthStatusImpl implements HealthStatus {
#Autowired
RestTemplateConfig restTemplateConfig;
#Autowired
private EVerifyGovernmentProperties eVerifyGovernmentProperties;
#Override
public Integer healthCheck() {
Integer status = 0;
try {
ResponseEntity<String> healthResponse = restTemplateConfig.getRestTemplate().exchange(eVerifyGovernmentProperties.getUrl(), HttpMethod.GET, null, String.class);
status = (healthResponse.getStatusCode() == HttpStatus.OK) ? 200 : 202;
} catch (Throwable th) {
th.printStackTrace();
}
return status;
}
}
Here is the class which instantiates the RestTemplate
#Component
public class RestTemplateConfig {
#Autowired
ProxyConfigProperties proxyConfigProperties;
public RestTemplate getRestTemplate(){
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
if (proxyConfigProperties.getEnabled()) {
Proxy proxy = new Proxy(java.net.Proxy.Type.HTTP, new InetSocketAddress(proxyConfigProperties.getUrl(), proxyConfigProperties.getPort()));
simpleClientHttpRequestFactory.setProxy(proxy);
}
RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(simpleClientHttpRequestFactory));
return restTemplate;
}
}
Actually this is what you want to mock :
restTemplateConfig.getRestTemplate().exchange(eVerifyGovernmentProperties.getUrl(), HttpMethod.GET, null, String.class);
but this will require deep mocking and that is also a bad smell : you should not need to define such a statement to invoke the rest template. This responsibility should be defined in a specific class.
So move it into a method of a specific bean for example RestTemplateService that will relieve you from passing as many as parameters and it will also balance better responsibility of this class by reducing its dependencies :
ResponseEntity<String> healthResponse = restTemplateService.getForHealthCheck();
Now just mock it with Mockito.
Concerning RestTemplateService, you could create your own class or rely on Feign (here Spring Feign makes more sense) that enables declarative rest client via interface.
It would give :
public class HealthStatusImpl implements HealthStatus {
private RestTemplateService restTemplateService;
// favor constructor injection
public HealthStatusImpl(RestTemplateService restTemplateService){
this.restTemplateService = restTemplateService;
}
#Override
public Integer healthCheck() {
Integer status = 0;
try {
ResponseEntity<String> healthResponse = restTemplateService.getForHealthCheck();
status = healthResponse.getStatusCode().is2xxSuccessful() ? 200 : 400;
} catch (Throwable th) {
th.printStackTrace();
}
return status;
}
}
Note that Status.is2xxSuccessful() is generally better as it returns true for any successful response (200, 201, etc..). And if it is not successful you want to return an error response code.
From the unit test side, you should mock this dependency and record a mock behavior according to your scenarios.
Note that in your case, you don't want to load a whole spring context but you want to perform a plain unit test, that is without container. So don't use #SpringBootTest but only JUnit and Mockito.
For example with JUnit 5 :
#ExtendWith(MockitoExtension.class)
public class HealthStatusImplTest{
private HealthStatusImpl healthStatusImpl;
#Mock
private RestTemplateService restTemplateServiceMock;
#BeforeEach
public void beforeEach(){
healthStatusImpl = new HealthStatusImpl(restTemplateService);
}
#Test
public void healthCheck_when_200_is_returned(){
Mockito.when(restTemplateServiceMock)
.getForHealthCheck().thenReturn(new ResponseEntity(HttpStatus.OK));
assertEquals(200, healthStatusImpl.healthCheck());
}
#Test
public void healthCheck_when_200_is_not_returned(){
Mockito.when(restTemplateServiceMock)
.getForHealthCheck().thenReturn(new ResponseEntity(HttpStatus.NOT_FOUND));
assertEquals(400, healthStatusImpl.healthCheck());
}
}
Of course the RestTemplateService should also be unitary test and unitary tests don't relieve from writing integration tests for higher level components.
To unit test a web client such as a RestTemplate based class, you need a framework which mocks the server, e.g.
the one from Spring: MockRestServiceServer:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-rest-client
https://www.baeldung.com/spring-mock-rest-template
WireMock: http://wiremock.org/docs/getting-started/
I'm not sure that your check (healthResponse.getStatusCode() == HttpStatus.OK) is correct, because if a status is not 2xx RestTemplate throws HttpStatusCodeException.
That's why it's always better to have some mocks if you integrate with a third party side.
That's why I also recommend you to consider MockRestServiceServer in your tests. see #Puce answer for links.
Also, there is no necessity to create a new RestTemplate for each request.
That's why I also recommend you to consider constructor injection. see #davidxxx answer for refactoring approaches. And don't forget to put connection timeouts in your RestTemplate settings.