test-manual-mock-cleanup
manual mock cleanup in a lifecycle hook, which the Jest config already does
The argument
Mock Cleanup
Mock cleanup is handled by Jest config, not per-test code. Set these in the package's Jest config:
// jest.config.js / jest.config.ts
{
clearMocks: true, // clear call tracking (calls, instances, results) before each test
restoreMocks: true, // restore jest.spyOn originals before each test
}
With these set, every mock starts each test with clean call tracking and its setup() factory wires the return value fresh. Do not add manual mockClear() calls or a cleanup beforeEach — the config does it.
clearMocks: true— clearscalls,instances,contexts, andresultsbefore each test (equivalent tojest.clearAllMocks()). It does not clearmockReturnValue/mockImplementation— that isresetMocks. Because every test re-sets its return values insetup(),clearMocksis sufficient and avoids wiping implementations; reach forresetMocksonly if a package genuinely needs return values auto-cleared.restoreMocks: true— additionally restores the original implementation of everyjest.spyOnbefore each test (it does not affect standalonejest.fn()return values).
If the package's Jest config lacks these: do NOT add them. clearMocks changes behavior for every existing test in the package — any test relying on a mock set once at module scope or in beforeAll will break (live example: adding it to a real package broke 22 import-time-construction tests). A repo-wide behavior change is a human's decision, not a test task's side effect. Instead:
- Build fresh
jest.fn()mocks inside eachsetup()factory call (and construct a fresh subject per call), so call tracking cannot accumulate across tests without any config or hooks. - For module-level mocks that must persist (a
jest.mockfactory), reset them at the top ofsetup()(.mockReset()+ re-wire), or assert only withtoHaveBeenCalledWith— positive assertions are unaffected by accumulated calls; avoidnot.toHaveBeenCalledon shared mocks. - Record the missing config as friction (
area: "environment") so the repo owner can adopt it deliberately.
The proof
import { expect, describe, test, jest, beforeEach } from '@jest/globals';
const mockGetTimezone = jest.fn<() => string>();
describe('subject', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('reads the timezone', () => {
mockGetTimezone.mockReturnValue('UTC');
expect(mockGetTimezone()).toBe('UTC');
});
});
import { expect, describe, test, jest } from '@jest/globals';
const mockGetCurrency = jest.fn<() => string>();
const setupCurrency = () => {
mockGetCurrency.mockReset();
mockGetCurrency.mockReturnValue('GBP');
return { currency: 'GBP' };
};
describe('subject', () => {
test('reads the currency', () => {
const { currency } = setupCurrency();
expect(mockGetCurrency()).toBe(currency);
});
});
Turn it down
Both lines go in your lightsout.config.json.
"standards-checks": { "test-manual-mock-cleanup": "advisory" }"standards-checks": { "test-manual-mock-cleanup": "off" }