Teaches the agent the right way to mock in Jest — jest.fn, mockImplementation, mockResolvedValue, jest.mock factories, spyOn with restore, and isolating modules like axios.
Teaches the agent the right way to mock in Jest — jest.fn, mockImplementation, mockResolvedValue, jest.mock factories, spyOn with restore, and isolating modules like axios.
This skill makes the agent mock dependencies in Jest deliberately and reversibly: stubbing functions with jest.fn, controlling return values with mockReturnValue/mockResolvedValue, replacing whole modules with jest.mock factories, and spying on real implementations with jest.spyOn (always restored). The guiding rule: mock the boundary, not the unit under test, and always reset state between tests so mocks never leak.
Use this skill when the agent needs to isolate code from the network, the clock, the filesystem, a database client, or any third-party module (axios, fs, a payment SDK).
Core Principles
Mock at the boundary. Mock network/DB/3rd-party clients, not the function you are testing. If you mock the thing under test, the test proves nothing.
Reset mocks between tests. Configure clearMocks: true (or call jest.clearAllMocks() in beforeEach) so call counts and implementations never bleed across tests.
jest.mock is hoisted. Calls to jest.mock('module', factory) are lifted above imports. The factory cannot reference outer variables unless they are prefixed mock.
Prefer spyOn + mockRestore over jest.mock when you only need to override one method and want the real implementation back afterward.
Type your mocks. Use jest.mocked() (or as jest.Mock) so the mock API is type-checked and autocompletes.
Assert behavior, then interactions. Check the result first; use toHaveBeenCalledWith to verify the boundary was called correctly.
Workflow / Patterns
Pattern 1 — jest.fn and controlling return values
jest.fn() is a recording stub. Drive it with mockReturnValue, mockResolvedValue, mockRejectedValue, or queue per-call values with mockReturnValueOnce.
Pattern 3 — jest.mock with a factory (the hoisting rule)
jest.mock replaces an entire module. Because it is hoisted above imports, any variable the factory references must be named with a mock prefix.
// notifier.tsimport { sendSms } from'./sms-client';
exportasyncfunctionnotify(phone: string, msg: string) {
awaitsendSms(phone, msg);
return`sent to ${phone}`;
}
// notifier.test.tsimport { notify } from'./notifier';
import { sendSms } from'./sms-client';
// Hoisted: the factory may only use `mock`-prefixed outer vars.const mockSend = jest.fn();
jest.mock('./sms-client', () => ({
sendSms: (...args: unknown[]) =>mockSend(...args),
}));
beforeEach(() => jest.clearAllMocks());
test('notify calls the SMS client correctly', async () => {
mockSend.mockResolvedValue(undefined);
const result = awaitnotify('+15551234567', 'Hello');
expect(result).toBe('sent to +15551234567');
expect(sendSms).toHaveBeenCalledWith('+15551234567', 'Hello');
expect(sendSms).toHaveBeenCalledTimes(1);
});
Pattern 4 — jest.spyOn with restore (override one method, keep the rest)
spyOn wraps a real method so you can assert on it and optionally stub it. Always restore — restoreMocks: true in config, or mockRestore().
import * as mathUtils from'./math-utils';
afterEach(() => jest.restoreAllMocks());
test('spy that still calls through', () => {
const spy = jest.spyOn(mathUtils, 'add'); // real impl runsconst sum = mathUtils.add(2, 3);
expect(sum).toBe(5);
expect(spy).toHaveBeenCalledWith(2, 3);
});
test('spy that replaces the implementation', () => {
jest.spyOn(mathUtils, 'add').mockReturnValue(42);
expect(mathUtils.add(2, 3)).toBe(42); // stubbed// restoreAllMocks() puts the real add back after this test.
});
test('spy on Date.now for deterministic time', () => {
jest.spyOn(Date, 'now').mockReturnValue(1_700_000_000_000);
expect(Date.now()).toBe(1_700_000_000_000);
});