how to junit test on restTemplate? - java

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

Related

external URL with API endpoint in SpringBoot [duplicate]

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

Mockito.When not working for custom RestTemplate

I need to do testing for the following service. When I try to do Mockito.When for custom restTemplate I get the error "org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);".
However, when I don't do when(env.getProperty("test1")).thenReturn("test"); I get the error "I/O error on POST request for "/null/Test/null": null;"
How to make test properly for such service ?
Test class
#InjectMocks
private AccountService _accountService;
#Mock
private RestTemplate _restTemplate;
#Test
public void shouldGetAccount_200() {
//when(_accountService.getRestTemplate()).thenReturn(_restTemplate);
when(env.getProperty("test1")).thenReturn("test");
when(env.getProperty("test2")).thenReturn("2");
when(_restTemplate.exchange(eq(String.format("%s/Test/%s", env.getProperty("test1"), env.getProperty("test2")), _accId), eq(HttpMethod.GET), any(), eq(TestRequest.class)))
.thenReturn(new ResponseEntity<>(_resp, HttpStatus.OK));
assertThat(_accountService.getAcc(_accId, "test", "test", "test")).isEqualTo(_resp);
}
Service
public IResponse getAcc(String accId, String sessionId, String apiKey, String userName) {
RestTemplate restTemplate = getRestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.COOKIE, sessionId);
headers.add("X-Forwarded-For", getIPAddress());
headers.add("Authorization", String.format("PS-Auth key=%s; runas=%s;", apiKey, userName));
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(null, headers);
ResponseEntity<TestRequest> response;
return restTemplate.exchange(String.format("%s/Test/%s", env.getProperty("test1"), env.getProperty("test2"))), accId),HttpMethod.GET, entity, TestRequest.class);
}
public RestTemplate getRestTemplate() {
SystemDefaultRoutePlanner routePlanner = new SystemDefaultRoutePlanner(ProxySelector.getDefault());
CloseableHttpClient httpClient = HttpClients.custom().setRoutePlanner(routePlanner)
.setConnectionManager(poolingHttpClientConnectionManager).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
List<ClientHttpRequestInterceptor> interceptorList = restTemplate.getInterceptors();
if (tracingClientHttpRequestInterceptor != null) {
interceptorList.add(tracingClientHttpRequestInterceptor);
interceptorList.add(traceLogClientHttpRequestInterceptor);
restTemplate.setInterceptors(interceptorList);
}
return restTemplate;
}
Using when() you assign the behavior to the mock that Mockito creates for you. This mock, however, never gets used since your getAcc() method calls the getRestTemplate that always creates a new instance of RestTemplate.
The obvious solution here is to use the put the getRestTemplate method in your configuration class and use it as a bean factory, autowiring* the RestTemplate bean into your AccountService. Then your #InjectMocks annotation will work as expected and you'll see that the service under test does indeed use the provided mock object.
*remember, there's three ways you can inject a bean in Spring - using field injection, method injection or constructor. That's not difficult to look up online so, I'll just say that most use-cases are handled best using constructor injection:
#Autowired
public AccountService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
Secondly, you don't show this but I'm pretty sure that env variable in getAcc is some kind of environment variables holder and is also not injected into the service properly so it doesn't get mocked. I would generally recommend to stick to #ConfigurationProperties (and you could easily mock those), or at least look into MockEnvironment.
As an aside, I can't think of why a service should need a new RestTemplate instance every time, and creating the RestTemplate is definitely beyound the scope of AccountService, so moving that to configuration also improves your code's structure. As a bonus, you can reuse the same RestTemplate bean in other services should you need that.
Evetually you might want to add a layer of abstraction making a separate bean to take care of constructing the url and adding the necessary headers to the RestTemplate request, so that the getAcc method gets more readable.

Is it possible to retrieve value from application.properties without autowiring the class under test?

I have a test class that is annotated with #Spy and #InjectMocks and tested using Mockito. The class under test has a value (url) that is retrieved from the application.properties file. I'd like to test whether this url is being set correctly within the method that uses it. I can do this if I remove the #Spy and #InjectMocks annotations and use #Autowire and #SpringBootTest. However, that breaks other tests that use the spy functionality, so I'm just wondering if there's any way we can keep the spy working and test all our methods inside the same file, maybe without the need to bring in the #SpringBootTest annotation and the autowiring? The workaround we're using for now is to have two files, one that uses the spy and the other that tests the specific method to do with the properties file and that requires the full context to load, but not sure that's the best solution here?
Here is the test with the spy:
#ExtendWith(MockitoExtension.class)
class ProviderHelperServiceTest {
#Spy
#InjectMocks
ProviderHelperService providerHelperService;
#Value("${viaduct-url}")
String viaductUrl;
#Test
void testGetRequestBodyUriSpec() {
WebClient.RequestBodyUriSpec requestBodyUriSpec = providerHelperService.getRequestBodyUriSpec("sifToken");
final String[] url = new String[1];
requestBodyUriSpec.attributes(httpHeaders -> {
url[0] = (String) httpHeaders.get("org.springframework.web.reactive.function.client.WebClient.uriTemplate");
});
// Fails as url[0] comes back as null. Disabled and moved to another file.
assertEquals(viaductUrl, url[0]);
}
#SpringBootTest
class ProviderHelperService2Test {
#Autowired
ProviderHelperService providerHelperService;
#Value("${viaduct-url}")
String viaductUrl;
#Test
void testGetRequestBodyUriSpec() {
WebClient.RequestBodyUriSpec requestBodyUriSpec = providerHelperService.getRequestBodyUriSpec("sifToken");
final String[] url = new String[1];
requestBodyUriSpec.attributes(httpHeaders -> {
url[0] = (String) httpHeaders.get("org.springframework.web.reactive.function.client.WebClient.uriTemplate");
});
assertEquals(viaductUrl, url[0]);
}
}
And here is the method under test:
public class ProviderHelperService {
#Value("${viaduct-url}")
String viaductUrl;
public WebClient.RequestBodyUriSpec getRequestBodyUriSpec(String sifToken) {
WebClient.RequestBodyUriSpec requestBodyUriSpec = WebClient.create().post();
requestBodyUriSpec.header("Authorization", sifToken);
requestBodyUriSpec.uri(viaductUrl);
return requestBodyUriSpec;
}
}
The cleanest way to perform such tests is to replace field injection with constructor injection, and then you can quite easily confirm that the value that's passed into the class comes back out the service call.
If you're using Boot, it's usually best to replace use of #Value with #ConfigurationProperties. Depending on the specifics, you can either pass the whole properties object to the service's constructor or write an #Bean configuration method that unpacks the relevant properties and passes them as plain constructor parameters with new.

Test RestTemplate Interceptors

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.

Spring boot mocked object returning null on call

I am using #RunWith(SpringRunner.class) to writing unit test case to mock the object. I am trying to mock the repository instance which is accepting request object and returning response, but in unit test case implementation I have mocked the repository using #MockBean annotation and register the it's method call using Mockito.when(respository.post(request)).thenReturn(response). But this call is returning null.
I faced similar situation, the problem is given parameter in Mockito.when() block may not be the same as spring generated. I'll explain my case below, hope to help you:
Product product = new Product(..);
Mockito.when(service.addProduct(product)).thenReturn(saveProduct)
When I send a request, spring generates new Project object which has same fields with product but instance is different. That is, Mockito cannot catch when statement. I changed it like below and it's worked:
Mockito.when(service.addProduct(Mockito.any())).thenReturn(savedProduct)
I figured it out. But the solution is still weird to me...
I was facing this issue because, I was instantiating the request and response in #Before annotated method... as describing below.
#Before
public void setup() {
Request reqA = new Request();
reqA.set..(..);
Response res = new Response();
res.set..(..);
Mockito.when(this.respository.post(reqA)).thenReturn(res);
}
#Test
public void test() {
// Creating Request instance again with all same properties.
// Such that this req instance is technically similarly as instantiated in #Before annotated method (above).
// By, implementing the equals and hashCode method.
Request reqB = new Request();
reqB.set..(..);
// Getting res as 'null' here....
Response res = this.service.post(reqB);
}
Since, reqA and reqB are technically similar then why mocked call not returning the same response as registered.
If I moved setup() method code inside test() method every thing starts working !!!!!
I had the same issue here, vsk.rahul's comment helped me a lot.
I was trying to to use a method to return a mock interaction, but not succeeded, turning it into a static method gave me the expected behavior.
Problem:
The method bar.foo() was returning null for any interaction
public void test1() {
doReturn(mockReturn()).when(bar).foo();
}
private String mockReturn() {
return "abc";
}
Solution:
The method bar.foo() is returning abc String for any interaction
public void test1() {
doReturn(mockReturn()).when(bar).foo();
}
private static String mockReturn() {
return "abc";
}

Categories

Resources