| name | testing-patterns |
| description | Vitest mocking reference for constructor mocks, global mocks, and env var mocks. Use when writing or fixing test mocks, when a test throws TypeError about constructors, when global mocks leak between tests, or when env var mocking causes undefined values. |
Testing Patterns
Mocking reference for Vitest. Jump to the section matching your problem.
Constructor Mocking (Vitest 4)
Vitest 4 enforces that mocks used with new must have a function or class implementation. Arrow functions lack
[[Construct]] and will throw TypeError: ... is not a constructor.
Anti-patterns
vi.mock('@google-cloud/storage', () => ({
Storage: vi.fn(() => ({ bucket: vi.fn() })),
}));
vi.mocked(SomeClass).mockReturnValue(instance);
Correct patterns
vi.mock('@google-cloud/storage', () => ({
Storage: vi.fn().mockImplementation(function () {
return { bucket: vi.fn() };
}),
}));
const MockClass = vi.hoisted(() =>
vi.fn().mockImplementation(function () {
return { method: vi.fn() };
})
);
mockConstructor.mockImplementation(function () {
return { bucket: vi.fn() };
});
Checklist for constructor mocks
Global Mocking
NEVER override global objects at file scope without proper restoration. Global mocks leak between test files.
Anti-patterns
global.fetch = vi.fn();
window.location = { pathname: '/test' } as any;
afterEach(() => { delete (window as any).location; });
(global.fetch as any).mockResolvedValue(response);
Correct: scoped mocks with restoration
describe('MyComponent', () => {
let fetchSpy: MockInstance;
let originalLocation: Location;
beforeEach(() => {
originalLocation = window.location;
fetchSpy = vi.spyOn(global, 'fetch').mockImplementation(vi.fn());
Object.defineProperty(window, 'location', {
value: { pathname: '/test/path' },
writable: true,
configurable: true,
});
});
afterEach(() => {
fetchSpy.mockRestore();
Object.defineProperty(window, 'location', {
value: originalLocation,
writable: true,
configurable: true,
});
});
});
Checklist for global mocks
Mocking Environment Config Modules
NEVER use vi.mock('env-module', () => ({ ONLY_VARS_I_NEED })) — this replaces the entire module and any transitive
dependency importing an unlisted var gets undefined.
Static mocks (vi.mock) — use importOriginal
vi.mock('your-project/config/env', async (importOriginal) => {
const actual = await importOriginal<typeof import('your-project/config/env')>();
return { ...actual, BUCKET_NAME: 'test-bucket' };
});
Dynamic mocks (vi.doMock) — spread shared defaults
vi.doMock doesn't support importOriginal. Use a shared test defaults module (see context.md for project-specific
path):
vi.doMock('your-project/config/env', async () => {
const { TEST_ENV_DEFAULTS } = await import('../test/testEnvDefaults');
return { ...TEST_ENV_DEFAULTS, SOME_KEY: 'override' };
});
Dynamic getters for mid-test changes
When a test needs to change an env var between assertions, use a getter with vi.hoisted:
const envMock = vi.hoisted(() => ({ ENVIRONMENT: 'test' as string }));
vi.mock('your-project/config/env', async (importOriginal) => {
const actual = await importOriginal<Record<string, unknown>>();
return {
...actual,
get ENVIRONMENT() { return envMock.ENVIRONMENT; },
};
});
Project Context
Read context.md and apply it as additional project-specific constraints layered on top of this
workflow. If it does not exist, skip this section.