Test Doubles

Test doubles are one of the main concepts we use to create fast, independent, deterministic and reliable tests. Similar to the way Hollywood uses a _stunt double* to film dangerous scenes in a movie to avoid the costly risk a high paid actor gets hurt, we use a test double in early test stages to avoid the speed and dollar cost of using the piece of software the test double is standing in for. We also use test doubles to force certain conditions or states of the application we want to test. Test doubles can be used in any stage of testing but in general, they are heavily used during the initial testing stages in our CD pipeline and used much less in the later stages. There are many different kinds of test doubles such as stubs, mocks, spies, etc.

Testing Glossary

Test Double

  • Test Double: A test double is a generic term for any case where you replace a production object for testing purposes.
  • Dummy: A dummy is passed around but never actually used. Usually it is just used to fill parameter lists.
  • Fake: A fake actually has a working implementation, but usually takes some shortcut which makes it not suitable for production (an InMemoryTestDatabase is a good example).
  • Stub: A stub provides canned answers to calls made during the test, usually not responding at all to anything outside what’s programmed in for the test.
  • Spy: A spy is a stub that also records some information based on how it was called. One form of this might be an email service that records how many messages it was sent.
  • Mock: A mock is pre-programmed with expectations which form a specification of the calls it is expected to receive. It can throw an exception if it receives a call it doesn’t expect and is checked during verification to ensure it got all the calls it was expecting.

Resources

Examples

    @Before
    public void init() throws Exception {

    // ===============Arrange===============
    userService = Mockito.spy(userService);
    ObjectMapper mapper = new ObjectMapper();
    // Here we are putting data from user_spy.json
    spyData = mapper.readValue(new File(TestConstants.DATA_FILE_ROOT + "user_spy.json"), User.class);
    Mockito.doReturn(spyData).when(userService).getUserInfo(TestConstants.userId);// spy json data for the user data
    }

    @Test
    // Mock the userService
    public void verifySpyUserDetails() throws Exception {
    
    // ===============Act===============
    User user = userService.getUserInfo(TestConstants.userId); // user data comes from spy
    verify(userService).getUserInfo(TestConstants.userId); // verify the userservice.getUserInfo method is called
    verify(userService, times(1)).getUserInfo(TestConstants.userId);// verify from spy the getUserInfo called one
    
    // ===============Assert===============
    // validate the expected spyData is matching with actual user Data
    Assert.assertEquals(spyData.getManager(), user.getManager());
    Assert.assertEquals(spyData.getVp(), user.getVp());
    Assert.assertEquals(spyData.getOrganization(), user.getOrganization());
    Assert.assertEquals(spyData.getDirector(), user.getDirector());
    Assert.assertEquals(spyData.getDirector(), user.getDirector());
    }

    @After
    public void cleanUp() {
    reset(userService);// Reseting the userServiceSpy
    }
  

Platform Independent Mocking Frameworks

Framework Reasoning
JSON-Server
  • Simple, great for scaffolding
  • Follows REST conventions
  • Stateful
Mountebank
  • Allows for more than just HTTP (multi-protocol)
  • Simple to use and configure
  • Large language support

GraphQL

Framework Reasoning
GraphQL-Faker
  • Supports proxying existing GraphQL APIs.
  • Simple GraphQL directive-based data mocking.
  • Uses faker.js under the hood.
GraphQL-Tools
  • Built-in utilities for mocking collections (MockList)
  • Great documentation and interoperability with existing GraphQL (NodeJS) solutions.

Platform Specific

Javascript

Framework Reasoning
expect(jest) For all generic assertions/mocking
jest-dom For DOM assertions
supertest For in-process test a http server
nock for http server endpoint assertion/mocking with NodeJS

For FE mocking, the recommendation is kept more open to allow for other frameworks as necessary, such as msw or mirage

Android

Framework Reasoning
MockK (Kotlin projects)
  • Provides a common when this →then that mocking API in an Idiomatic Kotlin DSL
  • Built in support for mocking top level functions, extensions, static objects
  • Detailed documentation with examples of how to mock and verify different cases
  • Concise and descriptive exception logs
  • Minimal configuration per TestClass (limited to resetting state)
MockWebServer
  • Process local mock server
  • Embedded in tests, no separate mock execution
  • Simplistic but powerful api that can support state
  • Easy to use. Start MWS before test, initialize netApi with the baseUrl of the MWS instance, configure in test’s // GIVEN phase, stop server after.

iOS

For iOS, Apple test frameworks support a rich feature set, documentation and community. As a team we prefer using 1P tooling and adding Homegrown solution on top of it. The reason we do this is because Apple has been notorious in changing API’s at rapid iterations. This also results us to constantly update 3P solutions which have a risk of getting discontinued and maintaining them is a challenge. Hence iOS team prefers to use more maintainable solution which would be 1P with additional Homegrown Utilities as required.

Java (BE)

Framework Reasoning
Powermock
  • Power mock is actually superset of Mockito.
  • Provides Static mocking functionality
Mockito
  • Standard mocking tool
  • Has annotations for easy creation of many mocks at test construction
Last modified December 15, 2023: Reorganize (7579932)