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: 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
}
Recommended 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.
|
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)