Introduction: Since Kent Beck put forward the concept of TDD(Test-driven Development) at the end of the last century, the boundary between Development and testing has become increasingly blurred. From the original dependence between upstream and downstream, it has gradually evolved into an interdependent relationship. Many companies have even created a new Quality Engineer position. Different from traditional Quality Assurance (QA), QE is mainly responsible for ensuring project Quality through engineering means, including but not limited to writing unit tests, integration tests, building automated test processes, and designing performance tests. It can be said that QE combines the quality awareness of QA with the engineering ability of development. Starting with this article, I will cover the basic skills required for QE as a test/development role from a development perspective in three installments.

1 What is a Mock?

In the world of software testing, a Mock means to simulate the behavior of a test object by some technical means in order to return a predesigned result. The key word here is pre-design, which means that for any object being tested, specific results can be returned according to the needs of specific test scenarios. For example, like the fake penguins in the BBC documentary, they can react differently depending on the filming needs.

2 What is the use of Mock?

Now that you understand what mocks are, let’s look at what they can be used for. First, mocks can be used to decouple test objects from external services (such as databases, third-party interfaces, etc.) so that test cases can run independently. This is especially important for both traditional monolithic applications and now popular microservices, because the presence of any external dependencies greatly limits the portability and stability of test cases. Portability means that if you want to run the same test case in a new test environment, you need to ensure that the test object itself can run properly, and that all dependent external services can be called properly. Stability means that the test case may fail if the external service is not available. By mocking out external dependencies, you can improve both the portability and stability of test cases.

The second benefit of mocks is that they replace external service calls and speed up test cases. Any external service invocation is at least cross-process level consumption, and even cross-system, cross-network consumption, while mocks reduce consumption to in-process consumption. For example, a second network request can be Mock down to the millisecond level, a difference of three orders of magnitude.

A third benefit of mocks is that they make testing more efficient. Test efficiency here has two implications. The first layer is the number of test cases run per unit of time, which is a direct benefit of increased speed. The second meaning is the number of test cases created per QE per unit of time. What to make of this second meaning? For a single application, as the service complexity increases, you may need to prepare a lot of test data to run a test case. At the same time, you need to ensure that the test data of multiple test cases does not interfere with each other. To do this, QE tends to spend a lot of time maintaining a working set of test data. With Mock, QE can easily achieve data isolation between different test cases by designing a separate set of test data for each or each set of test cases by removing database dependencies shared between test cases. With microservices, since one microservice may cascade on many other microservices, running a test case even requires preparing a set of test data across the system, which is virtually impossible without mocks. Therefore, with Mock, QE can save a lot of time preparing test data and focus on the test case itself, which naturally improves the efficiency of single-player testing.

3 How to Mock?

With all that said about mocks, how do you actually use them in your tests? You can choose different Mock frameworks for different test scenarios.

3.1 Mockito

If the test object is a method, especially one that involves database operations, then Mockito is probably the best choice. As the most widely used Mock framework, Mockito trumps EasyMock and is even integrated into Spring Testing by default. The idea is that CGLib dynamically generates a proxy object at run time for every class or object that is mocked, returning a predesigned result. The basic steps for integrating Mockito are:

  1. Mark the Mock class or object to generate a proxy object
  2. Customize the behavior of proxy objects through the Mockito API
  3. Call the methods of the proxy object to get the pre-designed results

Here’s an example from my sample project on GitHub,

@RunWith(SpringRunner.class)
@SpringBootTest
public class SignonServiceTests {

    // Test object, a service class
    @Autowired
    private SignonService signonService;

    // The Mock class, a DAO class that the service class depends on
    @MockBean
    private SignonDao dao;

    @Test
    public void testFindAll(a) {
        // SignonService#findAll() calls SignonDao#findAll()
        // If you do not customize, all Mock classes return null by default
        List<Signon> signons = signonService.findAll();
        assertTrue(CollectionUtils.isEmpty(signons));

        // Custom returns the result
        Signon signon = new Signon();
        signon.setUsername("foo");
        when(dao.findAll()).thenReturn(Lists.newArrayList(signon));

        signons = signonService.findAll();
        // Verify that the return result is consistent with the pre-designed result
        assertEquals(1, signons.size());
        assertEquals("foo", signons.get(0).getUsername()); }}Copy the code

As you can see from the test case above, with the DAO class that the Mock service class relies on, we can skip all database operations and customize the return results arbitrarily to focus on testing the business logic inside the service class. This is difficult to achieve with traditional non-mock tests.

Note: Mockito does not support Mock private or static methods, which can be used if you want to Mock such methodsPowerMock.

3.2 WireMock

If Mocketo is the Swiss Army knife that mocks Everything, WireMock is a sword for microservices. Unlike Mockito at the object level, WireMock targets apis. Suppose there are two microservices, service-a and service-b, and one of the apis in service-A (let’s call it apI-1) depends on service-b. Then, using traditional testing methods, testing apI-1 must start service-b at the same time. With WireMock, you can Mock out the service-b API on the service-a side of all dependencies to remove the service-b external dependency.

Also look at an example from my GitHub sample project,

@RunWith(SpringRunner.class)
@WebMvcTest(VacationController.class)
public class VacationControllerTests {

    // Another microservice on which the Mock is dependent
    @Rule
    public WireMockRule wireMockRule = new WireMockRule(3001);

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Before
    public void before(a) throws JsonProcessingException {
        // Custom returns the result
        JsonResult<Boolean> expected = JsonResult.ok(true);
        stubFor(get(urlPathEqualTo("/api/vacation/isWeekend"))
                .willReturn(aResponse()
                        .withStatus(OK.value())
                        .withHeader(CONTENT_TYPE, APPLICATION_JSON_UTF8_VALUE)
                        .withBody(objectMapper.writeValueAsString(expected))));
    }

    @Test
    public void testIsWeekendProxy(a) throws Exception {
        // Construct the request parameters
        VacationRequest request = new VacationRequest();
        request.setType(PERSONAL);
        OffsetDateTime lastSunday = OffsetDateTime.now().with(TemporalAdjusters.previous(SUNDAY));
        request.setStart(lastSunday);
        request.setEnd(lastSunday.plusDays(1));

        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/vacation/isWeekend");
        request.toMap().forEach((k, v) -> builder.param(k, v));
        JsonResult<Boolean> expected = JsonResult.ok(true);

        mockMvc.perform(builder)
                // Verify that the return result is consistent with the pre-designed result.andExpect(status().isOk()) .andExpect(content().contentType(APPLICATION_JSON_UTF8)) .andExpect(content().string(objectMapper.writeValueAsString(expected))); }}Copy the code

Similar to Mockito, the basic steps for integrating WireMock into a test case are:

  1. Declare a proxy service to replace the Mock microservice
  2. Customize the return result of the proxy service through the WireMock API
  3. Invoke the proxy service to get the pre-designed results

It’s worth noting that in addition to apI-style integration, WireMock also supports running independently as Jar packages that load pre-designed responses from configuration files in place of Mock microservices. More information can be found in the official documentation.

Other frameworks for similar Mock apis include OkHttp’s MockWebServer, Moco, and MockServer. Mockwebserver also falls into the category of embedded Mock frameworks, but its functionality is too simple. Moco and MockServer, while fully functional, require standalone deployment and have no advantage over WireMock.

4 summary

These are some of my insights on Mock techniques and you are welcome to share them on my message board. Finally, Mock techniques, while powerful, are mostly used for unit testing and are not used much in other testing areas such as integration testing, performance testing, automation testing, etc.

5 reference

  • Moving from Quality Assurance to Quality Engineering. A brief history in time and what lies ahead.