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.
Advertisement
Answer
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); } }