Issue
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.
Solution
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);
}
}
Answered By - mzlnk
Answer Checked By - Cary Denson (JavaFixing Admin)