A mocking framework, also called an isolation framework, is a library that creates, configures, and verifies test doubles at run time. It reduces the repetitive variables, functions, classes, and boilerplate needed for handwritten mocks and stubs, while helping isolate a unit of work from its dependencies. Despite the name, it supports both mocks, which verify interactions, and stubs, which simulate returned values or errors. section 5.0 section 5.1
Use one when a dependency makes a focused test awkward: a module imported directly, a function whose calls must be tracked, or a large interface that would otherwise need a handwritten fake. Frameworks can make those tests shorter and clearer, but easy faking can also hide strong coupling to third-party modules or produce interaction-heavy tests that are difficult to trust and maintain. section 5.2 section 5.4.1 section 5.6
A framework can support several levels of dependency isolation:
- Replace an imported module, configure its returned values, and verify calls made to it. This is useful when production code has hard module dependencies. section 5.2
- Create a tracked function with jest.fn(), then check its calls with a matcher such as toHaveBeenCalledWith(). section 5.3
- Build an object from individual tracked functions for an interface. Keep shared fake construction in a factory helper when several tests need it. section 5.4.1
- Generate a type-aware fake object and verify only the relevant call, reducing changes when its interface gains functions. section 5.4.2
Testing a logger interaction
A password verifier queries a configuration service for a logging level and commands a logger to record PASS or FAIL. Jest can replace both modules with jest.mock(), set getLogLevel to return "info", run verifyPassword, and assert that logger.info received text matching PASS. The test stubs the incoming configuration value and mocks the outgoing logging interaction. section 5.2 section 5.3
Listing 5.2 Faking the module APIs directly with jest.mock()
jest.mock("./complicated-logger"); #1
jest.mock("./configuration-service"); #1
const { stringMatching } = expect;
const { verifyPassword } = require("./password-verifier");
const mockLoggerModule = require("./complicated-logger"); #2
const stubConfigModule = require("./configuration-service"); #2
describe("password verifier", () => {
afterEach(jest.resetAllMocks); #3
test('with info log level and no rules,
it calls the logger with PASSED', () => {
stubConfigModule.getLogLevel.mockReturnValue("info"); #4
verifyPassword("anything", []);
expect(mockLoggerModule.info) #5
.toHaveBeenCalledWith(stringMatching(/PASS/)); #5
});
test('with debug log level and no rules,
it calls the logger with PASSED', () => {
stubConfigModule.getLogLevel.mockReturnValue("debug"); #6
verifyPassword("anything", []);
expect(mockLoggerModule.debug) #7
.toHaveBeenCalledWith(stringMatching(/PASS/)); #7
});
});
#1 Faking the modules
#2 Getting the fake instances of the modules
#3 Telling Jest to reset any fake module behavior between tests
#4 Configuring the stub to return a fake “info” value.
#5 Asserting that the mock was called correctly
#6 Changing the stub config
#7 Asserting on the mock logger as done previously
This listing shows module-level setup, stub configuration, mock verification, and reset cleanup in one short Arrange-Act-Assert test. section 5.2
Prefer a mock only when the important observable result is a call to a third-party dependency. Most tests can instead check a return value or state change, which assumes less about implementation details. When module faking is unavoidable in legacy code, use it deliberately; for new code, introduce abstractions over third-party modules to reduce refactoring when their APIs change. section 5.6.1 section 5.7
