Skip to content
Advertisement

How to test a Java/Spring service using RestTemplate without calling external API and by using Dependency Injection?

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);
    }

}
User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement