I want to implement RestTemplate service in Spring Boot that can work for multiple base URLs.
Here is my service class:
#Service
public class MyService {
private String baseUrl;
#Autowired
private RestTemplate restTemplate;
public ResponseEntity<String> method1() {...}
public ResponseEntity<String> method2() {...}
}
I am thinking how to inject that baseUrl when calling my service methods. Maybe I can create MyService constructor and get method to create new MyService instance:
public MyService(String baseUrl) {
this.baseUrl = baseUrl;
}
public static MyService get(String baseUrl) {
return new MyService(baseUrl);
}
But using this approach I will not be able to get RestTemplate autowired correctly.
So I am wondering what would be the best approach to solve my problem and not to create other classes?
You could skip setting baseUrl for RestTemplate and instead provide full url when doing requests:
var restTemplate = new RestTemplate();
restTemplate.exchange("https://google.pl", HttpMethod.GET, null, String.class);
Url's can be provided when constructing object, by creating a #Configuration class containing MyService definition:
#Bean
public RestTemplate restTemplate() {
// Creating rest template
}
#Bean
public MyService restTemplate() {
// var baseUrls = ...
// return new MyService(baseUrls, restTemplate())
}
My recommendation would be to change approach, assuming each baseUrl is a completely different external service, it would be best to inject multiple RestTemplate, each containing it's own baseUrl.
Additionally if possible, consider moving away from RestTemplate in favour of WebClient or OpenFeign, as RestTemplate is in maintenance mode:
<p><strong>NOTE:</strong> As of 5.0 this class is in maintenance mode, with
only minor requests for changes and bugs to be accepted going forward. Please,
consider using the {#code org.springframework.web.reactive.client.WebClient}
which has a more modern API and supports sync, async, and streaming scenarios.
Source: https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java
Related
In my Spring application, i have a service MyService. MyService calls an external API, counts the products there and returns the result. To call that API it uses the Spring module RestTemplate. To inject the RestTemplate it is configured as #Bean in the DependencyConfig:
#Service
public class ServiceImpl implements MyService {
private final RestTemplate restTemplate;
public ServiceImpl(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public String serve() {
ShopProductsResponse result = restTemplate.getForObject(
"https://api.predic8.de/shop/products/",
ShopProductsResponse.class
);
if (result == null) {
return "Error occurred";
}
return String.format("Found %s products", result.getProducts().length);
}
}
Now i want to test it, without calling the external API. So i inject the MyService via #Autowired and the RestTemplate via #MockBean. To define the result of restTemplate.getForObject(...) i use the Mockito.when(...).thenReturn(...) method to mock the result:
#SpringBootTest
class ServiceTest {
#MockBean
private RestTemplate restTemplate;
#Autowired
private MyService service;
#BeforeEach
void init() {
final ShopProductsResponse response = new ShopProductsResponse();
response.setProducts(new Product[0]);
Mockito.when(restTemplate.getForObject(Mockito.any(), Mockito.any())).thenReturn(response);
}
#Test
void test() {
final String expectation = "Found 0 products";
String result = service.serve();
Mockito.verify(restTemplate, Mockito.times(1)).getForObject(
ArgumentMatchers.eq("https://api.predic8.de/shop/products/"),
ArgumentMatchers.eq(ShopProductsResponse.class)
);
Assertions.assertEquals(expectation, result);
}
}
Problem is, that the result from restTemplate.getForObject(...) is null, so that the test fails with the message
org.opentest4j.AssertionFailedError:
Expected :Found 0 products
Actual :Error occurred
So my question is, what am i doing wrong? I thought i am telling the mock what to return. How would i do it correct?
If someone wants to try it out i pushed the example project to Github: https://github.com/roman-wedemeier/spring_example
Trying to find an answer in the net it was quite confusing to me, that there are different versions of Junit(4/5). Also somewhere i found a tutorial about mocking the service directly, which is not what i want to do. On the other hand someone explained how to mock the service's dependency but without using Spring's dependency injection.
restTemplate.getForObject() method has multiple set of parameters which can be invoked with:
restTemplate.getForObject(String url, Class<T> responseType, Object... uriVariables)
restTemplate.getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
restTemplate.getForObject(URI url, Class<T> responseType)
so you have probably mocked another method call by providing the widest matchers (Mockito.any()). I suggest you to try mock the restTemplate.getForObject() method call by providing more specific matchers, like for example:
Mockito.when(restTemplate.getForObject(Mockito.anyString(), Mockito.any())).thenReturn(response);
and the test should pass successfully.
Additionally, you can use just Mockito and DI to make unit tests for the the service instead of setting up the whole Spring application context (done by #SpringBootTest annotation). It is not necessary here and it only make the tests last much longer. Here is the alternative way to implement your test:
class ServiceTest {
private RestTemplate restTemplate;
private MyService service;
#BeforeEach
void init() {
final ShopProductsResponse response = new ShopProductsResponse();
response.setProducts(new Product[0]);
this.restTemplate = Mockito.mock(RestTemplate.class);
Mockito.when(this.restTemplate.getForObject(Mockito.anyString(), Mockito.any())).thenReturn(response);
this.service = new ServiceImpl(restTemplate);
}
#Test
void test() {
final String expectation = "Found 0 products";
String result = service.serve();
Mockito.verify(restTemplate, Mockito.times(1)).getForObject(
ArgumentMatchers.eq("https://api.predic8.de/shop/products/"),
ArgumentMatchers.eq(ShopProductsResponse.class)
);
Assertions.assertEquals(expectation, result);
}
}
I have the following scenario:
A Service calls some rest endpoint to query a resource.
The result of this query should be cached for one day and then requeried.
To verify this behavior I wrote a test that calls the service method 2 times and expects that the rest service was actually only queried one time.
Here would be the class that calls the endpoint:
#EnableCaching
#Service
public class CachedResourceClient {
private final RestTemplate restTemplate;
public CachedResourceClient(#Value("${api.host}") String apiHost, RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder
.rootUri(apiHost)
.build();
}
#Cacheable("resource")
public Optional<Resource> fetchResource() {
try {
return Optional.of(restTemplate.getForObject("/resource", Resource.class));
} catch (HttpClientErrorException | HttpServerErrorException | UnknownHttpStatusCodeException e) {
// logging
return Optional.empty();
}
}
}
and then we have this test here:
#RestClientTest(CachedResourceClient.class)
public class CachedResourceClientTests {
#Autowired
private MockRestServiceServer server;
#Autowired
private CachedResourceClient cachedResourceClient;
#Test
public void fetchResource() throws JsonProcessingException {
server.expect(requestTo("/resource"))
.andRespond(withSuccess("{ resource: 'some' }", MediaType.APPLICATION_JSON));
cachedResourceClient.fetchResource();
cachedResourceClient.fetchResource();
server.verify();
}
}
As you can see, we're calling the fetchResource method two times. But the server.verify call fails, because there are also two requests sent to the mock server.
I think the RestClientTest does something behind the scene with my CachedResourceClient that conflicts with the way Caching is setup.
Because if I remove the RestClientTest-Annotation and manually mock the RestTemplateBuilder and the RestTemplate the tests work as expected.
But having a server mock is a bit more comfortable than manually mocking the used classes.
Is there something wrong with the setup?
In this example of how to set up an Asynchronous service, for some reason the RestTemplate is set up in a very circuitous fashion.
Why can't the asynchronous routine itself just declare a new RestTemplate?
#Service
public class AsyncService {
private static Logger log = LoggerFactory.getLogger(AsyncService.class);
#Autowired
private RestTemplate restTemplate;
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
#Async("asyncExecutor")
public CompletableFuture<EmployeeNames> getEmployeeName() throws InterruptedException
{
log.info("getEmployeeName starts");
EmployeeNames employeeNameData = restTemplate.getForObject("http://localhost:8080/name", EmployeeNames.class);
log.info("employeeNameData, {}", employeeNameData);
Thread.sleep(1000L); //Intentional delay
log.info("employeeNameData completed");
return CompletableFuture.completedFuture(employeeNameData);
}
//...
Why can't the asynchronous routine itself just declare a new
RestTemplate?
Clearly no value here.
RestTemplate can be created simply with the new operator if not reused somewhere else.
Declaring it as #Bean makes sense if we want to reuse it somewhere else.
It indeed provides the singleton injectable/reusable in another bean that requires that.
But generally we don't do that in a #Service class like in this code but in a more global configuration class.
I am attempting to write a unit test for a generic service class like the following:
public class ApiService{
private RestTemplate restTemplate;
private ServiceDao serviceDao;
#Autowired
public ApiService(RestTemplate restTemplate, ServiceDao serviceDao) {
this.restTemplate = restTemplate;
this.serviceDao = serviceDao;
}
public ResponseEntity getObject(ObjectRequest request) {
// Service logic here
}
public ResponseEntity postObject(CreateObjectRequest request) {
// Service logic here
}
}
But am struggling with how to mock the restTemplate in the constructor of my service class such that when the test runs, data is not persisted.. I've looked into Mockito though don't see many examples or documentation regarding Mockito + TestNG in this context. Any help would be appreciated
First of all - if possible inject RestOperations in your service instead of RestTemplate. Then you will be able easily mock its behavior (note: RestTemplate implements RestOperations).
If using RestOperations is not possible - you can do something this:
RestTemplate myTemplate = Mockito.spy(new RestTemplate());
String expectedOutput = "hello mock";
String inputUrl = "https://stackoverflow.com/questions/53872148/unit-test-service-class-with-mocks-using-testng";
Mockito.doReturn(expectedOutput).when(myTemplate).getForObject(inputUrl, String.class);
String result = myTemplate.getForObject(inputUrl, String.class);
Assert.assertEquals(expectedOutput, result);
I've actually constructed a method using Mockito as follows... there might be a more elegant solution so I would be interested to see:
public class ServiceTest {
#BeforeMethod(groups="serviceTest")
public void beforeMethod() {
MockitoAnnotations.initMocks(this);
}
#Test(groups="serviceTest")
public void testGetService_returns200() {
when(serviceDao.getService(any(String.class), any(RestTemplate.class), any(HttpHeaders.class))).thenReturn(new ResponseEntity(new Object(), HttpStatus.OK));
ObjectRequest request = new ObjectRequest();
// set request values
ResponseEntity testResponse = apiService.getObject(request);
}
}
public class ServiceTest {
#Mock
RestTemplate restTemplate = new RestTemplate();
#InjectMocks
Service service = new Service();
ResponseEntity responseEntity = mock(ResponseEntity.class);
#Test
public void test() throws Exception {
Mockito.when(restTemplate.getForEntity(
Mockito.anyString(),
Matchers.any(Class.class)
))
.thenReturn(responseEntity);
boolean res = service.isEnabled("something");
Assert.assertEquals(res, false);
}
I tried to test a simple test for a service including a restclient. It looks I have not Mock the RestTemplate successfully. It looks like the code get the real data not the mock one. Anyone can help me with this.
The service itself will looks as this:
public class Service{
public boolean isEnabled(String xxx) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity("someurl",String.class);
if(...)return true;
return false;
}
}
The problem is that in your isEnabled you are creating a new RestTemplate. This is wrong for two reasons, one is that you cannot mock it since you are creating a new one, and second it is good to avoid creating new objects per request. RestTemplate is thread safe and hence can be a service class member, being used across many threads.
Change your service class to something like this:
public class Service{
RestTemplate restTemplate = new RestTemplate();
public boolean isEnabled(String xxx) {
ResponseEntity<String> response = restTemplate.getForEntity("someurl",String.class);
if(...)return true;
return false;
}
}
Now that your RestTemplate has become a class member you can now properly mock through one of two ways. One, inject it using the #InjectMock, or use a setter method that you call from your test.
Since you are using InjectMock in your code we can go with that.
#RunWith(MockitoJUnitRunner.class)
public class ServiceTest {
#Mock
RestTemplate restTemplate;
#InjectMocks
#Spy
Service service;
ResponseEntity responseEntity = mock(ResponseEntity.class);
#Test
public void test() throws Exception {
Mockito.when(restTemplate.getForEntity(
Mockito.anyString(),
ArgumentMatchers.any(Class.class)
))
.thenReturn(responseEntity);
boolean res = service.isEnabled("something");
Assert.assertEquals(res, false);
}
Notice that I made a few changes. First, I removed the new RestTemplate() and new Service(). You should let mockito create those for you. By annotating them with #Mock and #Spy you will ensure that Mockito will create them for you, and more importantly, will inject the mocks into your service object.
Spring MVC's test framework has offered the class MockRestServiceServer for unit testing RESTful service code.
Here is a tutorial on its use.
If you use #Autowired, you could use MockRestServiceServer.
The below is the sample.
#Service
public class Service{
#Autowired
private RestTemplate restTemplate;
public boolean isEnabled(String xxx) {
ResponseEntity<String> response = restTemplate.getForEntity("someurl",String.class);
if(...)return true;
return false;
}
}
#Service needs to use #Autowired for creating object automatically.