I have a method where I am injecting a value to an argument of the method. How can I call the checkHealth without passing in an argument that will overwrite the value value being injected?
private Health checkHealth(#Qualifier("myRestTemplate") RestTemplate restTemplate) {
}
#Bean(name = "myRestTemplate")
public RestTemplate myRestTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder();
return builder
.rootUri(uri)
.basicAuthentication("", "")
.build();
}
Well you cannot! Note that Spring's annotations work in its Spring's context and not when you manually try to do something. What it means is that, the injection will effectively happen if and only if Spring invokes the method checkHealth() and not when you call this method.
In your case, you do not need to pass in the argument. Simply call myRestTemplate() inside checkHealth() as:
private Health checkHealth() {
final RestTemplate template = myRestTemplate();
...
}
For your scenario to work, inject myRestTemplate as a field:
#Service
public class HealthServiceImpl implements HealthService {
#Autowired
#Qualifier("myRestTemplate")
private RestTemplate restTemplate;
private Health checkHealth() {
// do something with restTemplate
}
}
Otherwise, check #Prashant's answer on why injecting it as a method parameter does not work.
I ended up doing the below -
private RestTemplate restTemplate;
private Health checkHealth() {
ResponseEntity<String> response
= this.restTemplate.getForEntity(resourceUrl, String.class);
}
#Postconstruct
public RestTemplate myRestTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder();
return builder
.rootUri(uri)
.basicAuthentication("", "")
.build();
}
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);
}
}
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);
}
}
I'm using this configuration class to initialize RestTemplate:
#Configuration
public class RestTemplateConfig {
#Value("${endpoint-url}")
private String endpointUrl;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.rootUri(endpointUrl)
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
}
}
And in one of my service's method I use the code:
RootUriTemplateHandler handler = (RootUriTemplateHandler) restTemplate.getUriTemplateHandler();
String uri = handler.getRootUri();
restTemplate.postForLocation(uri, request);
To get this URI. Is there an easier method to get this rootUri (without casting)? Or to execute the post request directly to rootUri?
restTemplate.getUriTemplateHandler().expand("/")
You can execute the post request directly to rootUri, as long as the uri you provide to the restTemplate.postForLocation starts with "/". In that case, Spring will automatically add the baseURI provided in the restTemplate constructor.
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder,
#Value("${endpoint-url}") String endpointUrl) {
return builder
.rootUri(endpointUrl)
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
}
}
In your service's method:
// Make a POST call to ${endpoint-url}/foobar
String uri = "/foobar"; // The leading "/" is important!
restTemplate.postForLocation(uri, request);
Sounds like you are trying to use RestTemplate to pass along the value of ${endpoint-url}. That slightly awkward looking cast works but you could perhaps consider one of these alternatives:
Create a provider which encapsulates the endpointUrl and your restTemplate and inject this provider wherever you need either the endpointUrl or the restTemplate. For example:
#Component
public class RestTemplateProvider {
#Value("${endpoint-url}")
private String endpointUrl;
private final RestTemplate restTemplate;
#Autowired
public RestTemplateProvider(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.rootUri(endpointUrl)
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
}
public RestTemplate provide() {
return restTemplate;
}
public String getEndpointUrl() {
return endpointUrl;
}
}
Inject #Value("${endpoint-url}") private String endpointUrl; into which ever service class needs it.
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.