Mocking and Stubbing External REST APIs, Part One

After we have finished this webinar, we

  • Know how we can mock and stub components that use external REST APIs.
  • Can use these test doubles in our integration tests.
  • Understand the pros and cons of using Mockito.
  • Can identify the pros and cons of using classic stubs.
  • Can select the best tool for the job.

This webinar was held Sunday 7th of May, 2017 19:00 (Finnish time ).

Introduction to System Under Test

The system under test consists of four classes that are described in the following:

First, the VatPercentageController class provides a simple REST API that is used to find the VAT percentage which is used in a specific country.

When we want to get the used VAT percentage, we have to send a GET request to the path: ‘/api/vat-percentage’ and set the country code as the value of the countryCode request parameter.

The VatPercentageController class obtains the requested VatPercentage object by invoking the findByCountryCode() method of the VatPercentageService class and returns the found VatPercentage object.

The source code of the VatPercentageController class looks as follows:

 1import org.springframework.beans.factory.annotation.Autowired;
 2import org.springframework.web.bind.annotation.RequestMapping;
 3import org.springframework.web.bind.annotation.RequestMethod;
 4import org.springframework.web.bind.annotation.RequestParam;
 5import org.springframework.web.bind.annotation.RestController;
 6
 7@RestController
 8class VatPercentageController {
 9
10    private final VatPercentageService service;
11
12    @Autowired
13    VatPercentageController(VatPercentageService service) {
14        this.service = service;
15    }
16
17    @RequestMapping(value = "/api/vat-percentage", method = RequestMethod.GET)
18    VatPercentage findByCountryCode(@RequestParam("countryCode") String countryCode) {
19        return service.findByCountryCode(countryCode);
20    }
21}

Second, because these VAT percentages can change on a somewhat regular basis, we decided that we don’t really want to maintain these VAT percentages. Instead, we decided to use an external API. The VatPercentageService class gets the vat percentage from an external API by using a RestTemplate object. Its source code looks as follows:

 1import org.springframework.web.client.RestTemplate;
 2
 3public class VatPercentageService {
 4
 5    private final RestTemplate restTemplate;
 6
 7    public VatPercentageService(RestTemplate restTemplate) {
 8        this.restTemplate = restTemplate;
 9    }
10
11    public VatPercentage findByCountryCode(String countryCode) {
12        return restTemplate.getForObject(String.format(
13                "http://localhost:8080/api/external/vat-percentage?countryCode=%s",
14                countryCode
15            ),
16            VatPercentage.class
17        );
18    }
19}
This example uses a hard coded URL address because it fulfills the requirements of this example. However, I hope that you don’t use hard coded URL addresses in real-life applications.

Third, the VatPercentage class is a simple data transfer object that contains the country code and the VAT percentage of the country. Its source code code looks as follows:

 1public class VatPercentage {
 2
 3    private String countryCode;
 4    private Integer vatPercentage;
 5
 6    public VatPercentage() {}
 7
 8    public String getCountryCode() {
 9        return countryCode;
10    }
11
12    public Integer getVatPercentage() {
13        return vatPercentage;
14    }
15
16    public void setCountryCode(String countryCode) {
17        this.countryCode = countryCode;
18    }
19
20    public void setVatPercentage(Integer vatPercentage) {
21        this.vatPercentage = vatPercentage;
22    }
23}

Fourth, we don’t want to use an external API when we run our integration tests because this creates an unwanted dependency between the external API and our integration tests. In other words, our integration tests can fail because the API is down and it is possible that the network latency slows down our integration tests.

That’s why we have to ensure that the “real” VatPercentageService bean is available only when we run our application (the application Spring profile is active). We can configure the profile of a Spring bean by using the @Profile annotation.

Even though we can add this annotation directly to a class that is annotated with the @Component annotation, I recommend that we shouldn’t do it because it’s not easy to find all components that are registered only when a specific Spring profile is used. That’s why I think that we should configure these beans in our application context configuration class (or XML configuration file).

After we have configured the VatPercentageService bean, the source code of our application context configuration class looks as follows:

 1import com.testwithspring.intermediate.vat.VatPercentageService;
 2import org.springframework.boot.SpringApplication;
 3import org.springframework.boot.autoconfigure.SpringBootApplication;
 4import org.springframework.context.annotation.Bean;
 5import org.springframework.context.annotation.Profile;
 6import org.springframework.web.client.RestTemplate;
 7
 8@SpringBootApplication
 9public class ExampleApplication {
10
11    @Bean
12    RestTemplate restTemplate() {
13        return new RestTemplate();
14    }
15
16    @Bean
17    @Profile(Profiles.APPLICATION)
18    VatPercentageService vatPercentageService(RestTemplate restTemplate) {
19        return new VatPercentageService(restTemplate);
20    }
21
22    public static void main(String[] args) {
23        SpringApplication.run(ExampleApplication.class, args);
24    }
25}

Next, we will find out how we can create a VatPercentageService bean that doesn’t create a dependency between the external API and our integration tests. We will also learn how we can use this bean when we write our integration tests.

Let’s start by creating this bean with Mockito.

Stubbing Our Service With Mockito

If we want to create a stub that is managed by the Spring container, we have to create an application context configuration class that configures the application context of our integration tests. This class must import the configuration that is specified by the ExampleApplication class, and this class must also create a VatPercentageService stub that replaces the real VatPercentageService bean when we run our integration tests (The integrationTest Spring profile is used).

After we have created our application context configuration class, its source code looks as follows:

 1import org.springframework.context.annotation.Bean;
 2import org.springframework.context.annotation.Configuration;
 3import org.springframework.context.annotation.Import;
 4import org.springframework.context.annotation.Profile;
 5
 6import static com.testwithspring.intermediate.TestDoubles.stub;
 7
 8@Configuration
 9@Import({ExampleApplication.class})
10public class IntegrationTestMockitoStubContext {
11
12    @Bean
13    @Profile(Profiles.INTEGRATION_TEST)
14    VatPercentageService vatPercentageService() {
15        return stub(VatPercentageService.class);
16    }
17}

It’s important to realize that this class doesn’t use the stub() method of the Mockito class. The stub() method is provided by the TestDoubles class and its goal is to communicate our intentions to the other developers who might read our test code.

Also, typically we cannot configure our stub in our application context configuration class because it’s used by different integration tests which assume that the stubbed method returns different values.

After we have created the stub bean, we can write our integration tests by following these steps:

First, we have to create a test class and configure the Spring MVC Test framework. After we have done this, the source code of our test class looks as follows:

 1import org.junit.experimental.categories.Category;
 2import org.junit.runner.RunWith;
 3import org.springframework.beans.factory.annotation.Autowired;
 4import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 5import org.springframework.boot.test.context.SpringBootTest;
 6import org.springframework.test.context.ActiveProfiles;
 7import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 8import org.springframework.test.web.servlet.MockMvc;
 9
10@RunWith(SpringJUnit4ClassRunner.class)
11@SpringBootTest(classes = {IntegrationTestMockitoStubContext.class})
12@AutoConfigureMockMvc
13@Category(IntegrationTest.class)
14@ActiveProfiles(Profiles.INTEGRATION_TEST)
15public class FindVatPercentageMockitoStubTest {
16
17    @Autowired
18    private MockMvc mockMvc;
19}

Second, we have to add the required constants to our test class and inject the VatPercentageService stub to our test class. After we have made these changes to our test class, its source code looks as follows:

 1import org.junit.experimental.categories.Category;
 2import org.junit.runner.RunWith;
 3import org.springframework.beans.factory.annotation.Autowired;
 4import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 5import org.springframework.boot.test.context.SpringBootTest;
 6import org.springframework.test.context.ActiveProfiles;
 7import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 8import org.springframework.test.web.servlet.MockMvc;
 9
10@RunWith(SpringJUnit4ClassRunner.class)
11@SpringBootTest(classes = {IntegrationTestMockitoStubContext.class})
12@AutoConfigureMockMvc
13@Category(IntegrationTest.class)
14@ActiveProfiles(Profiles.INTEGRATION_TEST)
15public class FindVatPercentageMockitoStubTest {
16
17    private static final String COUNTRY_CODE = "FI";
18    private static final Integer VAT_PERCENTAGE = 24;
19
20    @Autowired
21    private MockMvc mockMvc;
22
23    @Autowired
24    private VatPercentageService service;
25}

Third, we have to add a setup method to our test class and implement this method by following these steps:

  1. Reset the state of our stub. It’s a good idea to do this because our stub is managed by the Spring container, and it might be used by other integration tests that require a different configuration.
  2. Configure the returned VatPercentage object.

After we have implemented the setup method, the source code of our integration test class looks as follows:

 1import org.junit.Before;
 2import org.junit.experimental.categories.Category;
 3import org.junit.runner.RunWith;
 4import org.mockito.Mockito;
 5import org.springframework.beans.factory.annotation.Autowired;
 6import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 7import org.springframework.boot.test.context.SpringBootTest;
 8import org.springframework.test.context.ActiveProfiles;
 9import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10import org.springframework.test.web.servlet.MockMvc;
11
12import static org.mockito.BDDMockito.given;
13
14@RunWith(SpringJUnit4ClassRunner.class)
15@SpringBootTest(classes = {IntegrationTestMockitoStubContext.class})
16@AutoConfigureMockMvc
17@Category(IntegrationTest.class)
18@ActiveProfiles(Profiles.INTEGRATION_TEST)
19public class FindVatPercentageMockitoStubTest {
20
21    private static final String COUNTRY_CODE = "FI";
22    private static final Integer VAT_PERCENTAGE = 24;
23
24    @Autowired
25    private MockMvc mockMvc;
26
27    @Autowired
28    private VatPercentageService service;
29
30    @Before
31    public void configureTestCases() {
32        resetStub();
33        returnVatPercentage();
34    }
35
36    private void resetStub() {
37        Mockito.reset(service);
38    }
39
40    private void returnVatPercentage() {
41        VatPercentage found = new VatPercentage();
42        found.setCountryCode(COUNTRY_CODE);
43        found.setVatPercentage(VAT_PERCENTAGE);
44
45        given(service.findByCountryCode(COUNTRY_CODE)).willReturn(found);
46    }
47}

Fourth, we have to create a private factory method that sends a GET request to the tested controller method and configures the value of the countryCode request parameter. This method takes the country code as a method parameter and returns a ResultActions object.

After we have implemented this method, the source code of our test class looks as follows:

 1import org.junit.Before;
 2import org.junit.experimental.categories.Category;
 3import org.junit.runner.RunWith;
 4import org.mockito.Mockito;
 5import org.springframework.beans.factory.annotation.Autowired;
 6import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 7import org.springframework.boot.test.context.SpringBootTest;
 8import org.springframework.test.context.ActiveProfiles;
 9import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10import org.springframework.test.web.servlet.MockMvc;
11import org.springframework.test.web.servlet.ResultActions;
12
13import static org.mockito.BDDMockito.given;
14import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
15
16@RunWith(SpringJUnit4ClassRunner.class)
17@SpringBootTest(classes = {IntegrationTestMockitoStubContext.class})
18@AutoConfigureMockMvc
19@Category(IntegrationTest.class)
20@ActiveProfiles(Profiles.INTEGRATION_TEST)
21public class FindVatPercentageMockitoStubTest {
22
23    private static final String COUNTRY_CODE = "FI";
24    private static final Integer VAT_PERCENTAGE = 24;
25
26    @Autowired
27    private MockMvc mockMvc;
28
29    @Autowired
30    private VatPercentageService service;
31
32    @Before
33    public void configureTestCases() {
34        resetStub();
35        returnVatPercentage();
36    }
37
38    private void resetStub() {
39        Mockito.reset(service);
40    }
41
42    private void returnVatPercentage() {
43        VatPercentage found = new VatPercentage();
44        found.setCountryCode(COUNTRY_CODE);
45        found.setVatPercentage(VAT_PERCENTAGE);
46
47        given(service.findByCountryCode(COUNTRY_CODE)).willReturn(found);
48    }
49
50    private ResultActions findByCountryCode(String countryCode) throws Exception {
51        return mockMvc.perform(get("/api/vat-percentage")
52                .param("countryCode", countryCode)
53        );
54    }
55}

Fifth, we have to write three integration tests for the tested controller method. These tests are:

  1. We have to verify that it returns the HTTP status code 200.
  2. We have to ensure that it returns the found VAT percentage as JSON.
  3. We have to verify that it returns the correct information.

After we have written our integration tests, the source code of our test class looks as follows:

 1import org.junit.Before;
 2import org.junit.Test;
 3import org.junit.experimental.categories.Category;
 4import org.junit.runner.RunWith;
 5import org.mockito.Mockito;
 6import org.springframework.beans.factory.annotation.Autowired;
 7import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 8import org.springframework.boot.test.context.SpringBootTest;
 9import org.springframework.http.MediaType;
10import org.springframework.test.context.ActiveProfiles;
11import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12import org.springframework.test.web.servlet.MockMvc;
13import org.springframework.test.web.servlet.ResultActions;
14
15import static org.hamcrest.Matchers.is;
16import static org.mockito.BDDMockito.given;
17import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
18import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
19import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
20import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
21
22@RunWith(SpringJUnit4ClassRunner.class)
23@SpringBootTest(classes = {IntegrationTestMockitoStubContext.class})
24@AutoConfigureMockMvc
25@Category(IntegrationTest.class)
26@ActiveProfiles(Profiles.INTEGRATION_TEST)
27public class FindVatPercentageMockitoStubTest {
28
29    private static final String COUNTRY_CODE = "FI";
30    private static final Integer VAT_PERCENTAGE = 24;
31
32    @Autowired
33    private MockMvc mockMvc;
34
35    @Autowired
36    private VatPercentageService service;
37
38    @Before
39    public void configureTestCases() {
40        resetStub();
41        returnVatPercentage();
42    }
43
44    private void resetStub() {
45        Mockito.reset(service);
46    }
47
48    private void returnVatPercentage() {
49        VatPercentage found = new VatPercentage();
50        found.setCountryCode(COUNTRY_CODE);
51        found.setVatPercentage(VAT_PERCENTAGE);
52
53        given(service.findByCountryCode(COUNTRY_CODE)).willReturn(found);
54    }
55
56    @Test
57    public void shouldReturnHttpStatusCodeOk() throws Exception {
58        findByCountryCode(COUNTRY_CODE)
59                .andExpect(status().isOk());
60    }
61
62    @Test
63    public void shouldReturnVatPercentageAsJson() throws Exception {
64        findByCountryCode(COUNTRY_CODE)
65                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
66    }
67
68    @Test
69    public void shouldReturnCorrectVatPercentage() throws Exception {
70        findByCountryCode(COUNTRY_CODE)
71                .andExpect(jsonPath("$.countryCode", is(COUNTRY_CODE)))
72                .andExpect(jsonPath("$.vatPercentage", is(VAT_PERCENTAGE)));
73    }
74
75    private ResultActions findByCountryCode(String countryCode) throws Exception {
76        return mockMvc.perform(get("/api/vat-percentage")
77                .param("countryCode", countryCode)
78        );
79    }
80}

Let’s move on and find out how we can mock our service with Mockito.

Mocking Our Service With Mockito

If we want verify that the system under test invokes a method of our test double, we have to use a mock instead of a stub. As we remember, every test double that is created with Mockito can be a mock. In other words, we could just write a new test method which verifies that the findByCountryCode() method of our stub is invoked by using the correct country code.

However, I think that if we want to verify interactions that happened between the system under test and our test double, we have to make sure that the readers of our test code understand that our test double is a mock. This means that we have to create our test double bean by invoking the mock() method of the Mockito class.

After we have done this, the source code of our application context configuration class looks as follows:

 1import org.springframework.context.annotation.Bean;
 2import org.springframework.context.annotation.Configuration;
 3import org.springframework.context.annotation.Import;
 4import org.springframework.context.annotation.Profile;
 5
 6import static org.mockito.Mockito.mock;
 7
 8@Configuration
 9@Import({ExampleApplication.class})
10public class IntegrationTestMockitoMockContext {
11
12    @Bean
13    @Profile(Profiles.INTEGRATION_TEST)
14    VatPercentageService vatPercentageService() {
15        return mock(VatPercentageService.class);
16    }
17}

After we have created our mock bean, we can write the integration test which verifies that the system under test invokes the findByCountryCode() method. Because we can reuse most of the code that we wrote earlier, I won’t repeat myself here. That being said, when we created a new test class, we made three changes to the configuration of our test cases:

  1. We changed the application context configuration class that configures the application context of our integration tests.
  2. If we don’t want to verify that the correct information is returned, we don’t have to stub the findByCountryCode() method.
  3. Our setup method simply resets the state of our mock bean.

After we have made these changes, we have to write one integration tests which verifies that the findByCountryCode() method is invoked once by using the correct country code.

The source code of our integration test class looks as follows:

 1import org.junit.Before;
 2import org.junit.Test;
 3import org.junit.experimental.categories.Category;
 4import org.junit.runner.RunWith;
 5import org.springframework.beans.factory.annotation.Autowired;
 6import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 7import org.springframework.boot.test.context.SpringBootTest;
 8import org.springframework.test.context.ActiveProfiles;
 9import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10import org.springframework.test.web.servlet.MockMvc;
11import org.springframework.test.web.servlet.ResultActions;
12
13import static org.mockito.Mockito.times;
14import static org.mockito.Mockito.verify;
15import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
16
17@RunWith(SpringJUnit4ClassRunner.class)
18@SpringBootTest(classes = {IntegrationTestMockitoMockContext.class})
19@AutoConfigureMockMvc
20@Category(IntegrationTest.class)
21@ActiveProfiles(Profiles.INTEGRATION_TEST)
22public class FindVatPercentageMockitoMockTest {
23
24    private static final String COUNTRY_CODE = "FI";
25
26    @Autowired
27    private MockMvc mockMvc;
28
29    @Autowired
30    private VatPercentageService service;
31
32    @Before
33    public void resetMock() {
34        Mockito.reset(service);
35    }
36
37    @Test
38    public void shouldFindVatPercentageWithCorrectCountryCode() throws Exception {
39        findByCountryCode(COUNTRY_CODE);
40
41        verify(service, times(1)).findByCountryCode(COUNTRY_CODE);
42    }
43
44    private ResultActions findByCountryCode(String countryCode) throws Exception {
45        return mockMvc.perform(get("/api/vat-percentage")
46                .param("countryCode", countryCode)
47        );
48    }
49}

We can now create stub and mock beans with Mockito and use these beans in our integration tests. Next, we will find out how we can create a classic stub and use it in our integration tests.

Creating a Classic Stub

If we want to implement a classic stub and use this stub in our integration tests, we have to follow these steps:

First, we have to create our classic stub by following these steps:

  1. Create a class that extends the VatPercentageService class.
  2. Implement a default constructor that passes null value to the constructor of the super class.
  3. Override the findByCountryCode() method.
  4. Implement the findByCountryCode() method by returning a constant VAT percentage for all countries.

After we have created our classic stub, its source code looks as follows:

 1public class VatPercentageServiceStub extends VatPercentageService {
 2
 3    private static final Integer VAT_PERCENTAGE = 24;
 4
 5    VatPercentageServiceStub() {
 6        super(null);
 7    }
 8
 9    @Override
10    public VatPercentage findByCountryCode(String countryCode) {
11        VatPercentage vatPercentage = new VatPercentage();
12        vatPercentage.setCountryCode(countryCode);
13        vatPercentage.setVatPercentage(VAT_PERCENTAGE);
14
15        return vatPercentage;
16    }
17}

Second, we have to create an application context configuration class that configures the application context of our integration tests. This class ensures that our classic stub is used when we run our integration tests.

After we have created our application context configuration class, its source code looks as follows:

 1import org.springframework.context.annotation.Bean;
 2import org.springframework.context.annotation.Configuration;
 3import org.springframework.context.annotation.Import;
 4import org.springframework.context.annotation.Profile;
 5
 6@Configuration
 7@Import({ExampleApplication.class})
 8public class IntegrationTestClassicStubContext {
 9
10    @Bean
11    @Profile(Profiles.INTEGRATION_TEST)
12    VatPercentageService vatPercentageService() {
13        return new VatPercentageServiceStub();
14    }
15}

Third, we have to create our test class, configure our integration tests, and write three integration tests for the tested controller method. Because I have already written these tests earlier, I won’t repeat myself here.

However, it is important to understand that if we use a classic stub, our integration tests don’t know that the system under test doesn’t invoke the external API. This makes our tests simpler and easier to maintain.

After we have written our integration tests, the source code of our integration test class looks as follows:

 1import org.junit.Test;
 2import org.junit.experimental.categories.Category;
 3import org.junit.runner.RunWith;
 4import org.springframework.beans.factory.annotation.Autowired;
 5import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 6import org.springframework.boot.test.context.SpringBootTest;
 7import org.springframework.http.MediaType;
 8import org.springframework.test.context.ActiveProfiles;
 9import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10import org.springframework.test.web.servlet.MockMvc;
11import org.springframework.test.web.servlet.ResultActions;
12
13import static org.hamcrest.Matchers.is;
14import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
15import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
16
17@RunWith(SpringJUnit4ClassRunner.class)
18@SpringBootTest(classes = {IntegrationTestClassicStubContext.class})
19@AutoConfigureMockMvc
20@Category(IntegrationTest.class)
21@ActiveProfiles(Profiles.INTEGRATION_TEST)
22public class FindVatPercentageClassicStubTest {
23
24    private static final String COUNTRY_CODE = "FI";
25    private static final Integer VAT_PERCENTAGE = 24;
26
27    @Autowired
28    private MockMvc mockMvc;
29
30    @Test
31    public void shouldReturnHttpStatusCodeOk() throws Exception {
32        findByCountryCode(COUNTRY_CODE)
33                .andExpect(status().isOk());
34    }
35
36    @Test
37    public void shouldReturnVatPercentageAsJson() throws Exception {
38        findByCountryCode(COUNTRY_CODE)
39                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
40    }
41
42    @Test
43    public void shouldReturnCorrectVatPercentage() throws Exception {
44        findByCountryCode(COUNTRY_CODE)
45                .andExpect(jsonPath("$.countryCode", is(COUNTRY_CODE)))
46                .andExpect(jsonPath("$.vatPercentage", is(VAT_PERCENTAGE)));
47    }
48
49    private ResultActions findByCountryCode(String countryCode) throws Exception {
50        return mockMvc.perform(get("/api/vat-percentage")
51                .param("countryCode", countryCode)
52        );
53    }
54}

Next, we will find out when we should use Mockito and when we should implement our own stub.

Which Technique Should We Use?

As always, both techniques have pros and cons, and we should always use the technique that fulfills our requirements.

Mockito has a very versatile support for both stubbing and mocking. In other words, Mockito allows us to take the total control of our test doubles. This might sound good, but the downside of using Mockito is that we have to reset our test doubles before a test method is run AND we must configure the stubbed methods in our test class.

Classic stubs are invisible to our integration tests and this makes them a good choice if our test double can always return the same value. The downside of classic stubs is that they are not very practical if we need proper argument matching.

I think that we can select the used technique by following these rules:

First, if we want to verify the interactions that happened between the system under test and our test double, we should create our mock bean with Mockito because we shouldn’t reinvent the wheel.

Second, if our stub must return different values or we want to emphasize the returned value in our test class, we should create our stub bean with Mockito. Mockito is the best tool for the job because it provides a quite versatile support for stubbing, and that’s why I think that we shouldn’t try to reinvent the wheel.

Third, if our stub can always return the same value, we should create a classic stub because it helps us to write simple integration tests that are easy to maintain.

Let’s summarize what we learned from this webinar.

Summary

This webinar has taught us three things:

  • If we have to verify the interactions that happened between the system under test and our test double, we should create our mock with Mockito.
  • If our test double must return different values, we should create our stub with Mockito.
  • If our test double can always return the same value, we should write a classic stub.

As we noticed, it is quite simple to replace the service class which uses an external API with a test double. The downside of this approach is that our integration tests ignore the code that uses the external API. My next webinar describes how we can solve this problem.

Get the source code from Github