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);
}
}
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 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();
}
I am writing a simple REST client, my service class has one method using
RestTemplate.getForObject()
I would like to practice testing but I don't really know if we should test the class. I also do not know how to test method that does not do much. Should I write any unit tests?
You can test, that request has been sent. For example you have the next service:
#Service
public void QueryService {
private final RestTemplate restTemplate;
#Autowired
public QueryService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public List<Employee> void makeQuery() {
ResponseEntity<List<Employee>> response = restTemplate.getForObject(
"http://localhost:8080/employees",
EmployeeList.class);
return response.getEmployees();
}
}
For the method makeQuery you can write the next unit test:
#RunWith(MockitoJUnitRunner.class)
public class QueryServiceTest {
#Mock
private RestTemplate restTemplate;
#InjectMocks
private QueryService queryService;
#Test
public void testMakeQueryRequestHasBeenSent() {
List<Employee> result = queryService.makeQuery();
// This string checks that method getForObject has been invoked
verify(restTemplate).getForObject(anyString(), any(Class.class));
}
}
In the future, if you will change makeQuery method and forget to send request using restTemplate, this test will fail.
I am trying to test a RestTemplate exchange with a response entity in a controller from a rest Service in another application. My Junit test is coming back with a null ResponseEntity. I have tried a couple ways with no luck. First tried using mockito to mock response methods (when...then). Second tried with Exchange matchers. I also tried to mix the two with no luck. Any help would be great. Here is my Controller response I am creating:
ResponseEntity<RestResult<List<SiteMessage>>> response = rest.exchange(getAllMessagesUrl, HttpMethod.GET,
HttpEntity.EMPTY, new ParameterizedTypeReference<RestResult<List<SiteMessage>>>() {
});
Here is my Junit Test:
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
public class MessageControllerTests {
#InjectMocks
MessageController messageController;
#Mock
RestTemplate restTemplate;
#Mock
SiteMessageService serviceMock;
#Mock
ResponseEntity<RestResult<List<SiteMessage>>> response;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(messageController).build();
}
#Test
public void testListPage() throws Exception {
RestResult<List<SiteMessage>> result = new RestResult<>();
result.setMessage("success");
SiteMessage message1 = new SiteMessage();
SiteMessage message2 = new SiteMessage();
message1.setId("ldsf030j2ldjs");
message2.setId("ldsf0432234s");
List<SiteMessage> messageList = new ArrayList<>();
messageList.add(message1);
messageList.add(message2);
result.setResults(messageList);
when(response.getBody()).thenReturn(result);
when(response.getStatusCode()).thenReturn(HttpStatus.NOT_FOUND);
when(restTemplate.exchange(
Matchers.any(URI.class),
Matchers.eq(HttpMethod.GET),
Matchers.any(),
Matchers.<ParameterizedTypeReference<RestResult<List<SiteMessage>>>>any())
).thenReturn(response);
mockMvc.perform(get("/message/list")).andExpect(status().isOk()).andExpect(view().name("message/list"));
}
}
I am trying to return a response with a body containing a RestResult object, which has a list of messages and a message
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.