Fix: Vitest Setup Not Working (setupFiles, Mocks, and Global Config Issues)
Quick Answer
How to fix Vitest configuration not taking effect — why setupFiles don't run, globals are undefined, mocks don't work, and how to configure Vitest correctly for React, Vue, and Node.js projects.
The Error
Your Vitest setup file is not running, or globals like describe and expect are undefined:
ReferenceError: describe is not defined
ReferenceError: expect is not definedOr mocks defined in setupFiles don’t apply in tests:
// setup.ts — mock defined here
vi.mock('./api/users');
// test.ts — mock not active
import { getUsers } from './api/users';
getUsers(); // Still calls real implementationOr a testing library setup (like @testing-library/jest-dom) isn’t loaded:
Error: expect(...).toBeInTheDocument is not a functionOr environment globals like window, document, or localStorage are undefined despite setting environment: 'jsdom'.
Why This Happens
Vitest’s configuration is separate from Vite’s and requires explicit setup. Common failure causes:
globals: truenot set — Vitest doesn’t exposedescribe,it,expectglobally by default. You must either import them or enableglobals: true.setupFilespath is wrong — the path is relative to the config file, not the test file.vi.mock()in setupFiles doesn’t apply per-file —vi.mock()insetupFileshoists differently than in test files. UsesetupFilesfor environment setup, not for mocking modules.- Wrong test environment — browser APIs (
window,document) requireenvironment: 'jsdom'orenvironment: 'happy-dom', which is not the default (node). - TypeScript types not configured —
describeandexpectexist at runtime but TypeScript reports them as undefined types.
Fix 1: Enable Globals and Configure the Environment
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
globals: true, // Exposes describe, it, expect, vi globally
environment: 'jsdom', // 'node' (default) | 'jsdom' | 'happy-dom'
setupFiles: ['./src/test/setup.ts'],
include: ['**/*.{test,spec}.{ts,tsx}'],
exclude: ['node_modules', 'dist'],
},
});Update TypeScript to recognise global types:
// tsconfig.json
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}Or add a reference in your setup file:
// src/test/setup.ts
/// <reference types="vitest/globals" />Install the jsdom environment if needed:
npm install --save-dev @vitest/ui jsdom
# or for happy-dom (faster alternative)
npm install --save-dev happy-domFix 2: Fix setupFiles Path and Usage
setupFiles runs before each test file. Use it for environment setup — not for vi.mock():
// vitest.config.ts
export default defineConfig({
test: {
setupFiles: [
'./src/test/setup.ts', // Path relative to project root (where vitest.config.ts lives)
],
},
});// src/test/setup.ts — correct uses for setupFiles
// 1. Extend expect matchers
import '@testing-library/jest-dom/vitest';
// or older API:
import '@testing-library/jest-dom';
import { expect } from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
expect.extend(matchers);
// 2. Set up global mocks for browser APIs
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation(query => ({
matches: false,
media: query,
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
})),
});
// 3. Reset mocks after each test
import { afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';
afterEach(() => {
cleanup(); // Unmount React components after each test
vi.clearAllMocks();
});Common Mistake: Putting
vi.mock('./api/users')insetupFiles. Mocks set up insetupFilesare not automatically reset between tests and don’t get the per-test hoisting thatvi.mock()in test files receives. Define mocks inside individual test files.
Fix 3: Fix vi.mock() Not Working in Test Files
vi.mock() must be called at the top level of a test file — Vitest hoists it automatically:
// ✗ Wrong — vi.mock inside a function or describe block
describe('UserService', () => {
vi.mock('./api/users'); // Not hoisted — runs after imports
});
// ✓ Correct — at the top level, before imports (Vitest hoists it)
vi.mock('./api/users', () => ({
getUsers: vi.fn().mockResolvedValue([{ id: 1, name: 'Alice' }]),
}));
import { getUsers } from './api/users';
describe('UserService', () => {
it('fetches users', async () => {
const users = await getUsers();
expect(users).toHaveLength(1);
});
});Mock with a factory function for default exports:
vi.mock('./logger', () => ({
default: {
info: vi.fn(),
error: vi.fn(),
},
}));
import logger from './logger';
// logger.info is now a vi.fn()Partially mock a module — keep real implementations:
vi.mock('./utils', async (importOriginal) => {
const actual = await importOriginal<typeof import('./utils')>();
return {
...actual, // Keep all real exports
formatDate: vi.fn(), // Override only this one
};
});Fix 4: Fix @testing-library/jest-dom Matchers
npm install --save-dev @testing-library/jest-dom @testing-library/react @testing-library/user-event// src/test/setup.ts
import '@testing-library/jest-dom/vitest'; // Vitest-specific import (preferred)// vitest.config.ts
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
},
});If toBeInTheDocument is still not recognised by TypeScript:
// src/test/setup.ts
import { expect } from 'vitest';
import * as matchers from '@testing-library/jest-dom/matchers';
expect.extend(matchers);
// Add type augmentation
declare module 'vitest' {
interface Assertion<R = any> extends jest.Matchers<R, any> {}
interface AsymmetricMatchersContaining extends jest.Matchers<any, any> {}
}Fix 5: Fix Per-File Environment Overrides
You can set the environment per file using a docblock comment — useful when most tests run in node but some need jsdom:
// LoginForm.test.tsx
// @vitest-environment jsdom
import { render, screen } from '@testing-library/react';
import LoginForm from './LoginForm';
test('renders login button', () => {
render(<LoginForm />);
expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument();
});Or configure environment per glob pattern in vitest.config.ts:
export default defineConfig({
test: {
environmentMatchGlobs: [
['**/*.browser.test.ts', 'jsdom'],
['**/*.node.test.ts', 'node'],
['src/components/**/*.test.tsx', 'jsdom'],
],
},
});Fix 6: Fix Path Aliases in Vitest
If your project uses @/ or other path aliases, configure them in Vitest too:
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
},
},
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
},
});If you already have a vite.config.ts, extend it instead of duplicating:
// vitest.config.ts
import { defineConfig, mergeConfig } from 'vitest/config';
import viteConfig from './vite.config';
export default mergeConfig(viteConfig, defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
},
}));Fix 7: Fix Common Vitest + React Testing Library Issues
act() warnings in the console:
// Wrap state-updating interactions
import { act } from '@testing-library/react';
test('updates on click', async () => {
render(<Counter />);
await act(async () => {
userEvent.click(screen.getByRole('button'));
});
expect(screen.getByText('1')).toBeInTheDocument();
});
// Or use userEvent.setup() which wraps in act automatically
const user = userEvent.setup();
await user.click(screen.getByRole('button'));window.ResizeObserver not defined:
// src/test/setup.ts
global.ResizeObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(),
unobserve: vi.fn(),
disconnect: vi.fn(),
}));IntersectionObserver not defined:
global.IntersectionObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(),
unobserve: vi.fn(),
disconnect: vi.fn(),
}));Still Not Working?
Run Vitest with --reporter=verbose to see exactly which files and setups load:
npx vitest --reporter=verboseCheck Vitest finds your config file. Vitest looks for vitest.config.ts, vitest.config.js, or the test key in vite.config.ts. If you have both a vite.config.ts and vitest.config.ts, the Vitest-specific file takes priority:
npx vitest --config vitest.config.tsVerify your Node.js version. Vitest requires Node.js 18+:
node --versionCheck for conflicting jest config. If your project previously used Jest, an old jest.config.js or jest key in package.json won’t affect Vitest — but leftover @types/jest can cause TypeScript conflicts with Vitest’s types:
npm uninstall @types/jest
npm install --save-dev @vitest/globalsFor related testing issues, see Fix: jest.mock() Not Working and Fix: Jest Test Timeout.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Cypress Not Working — Tests Timing Out, Elements Not Found, or cy.intercept Not Matching
How to fix Cypress issues — element selection strategies, async command chaining, cy.intercept for network stubbing, component testing, authentication handling, and flaky test debugging.
Fix: MSW (Mock Service Worker) Not Working — Handlers Not Intercepting, Browser Not Mocking, or v2 Migration Errors
How to fix Mock Service Worker issues — browser vs Node setup, handler registration, worker start timing, passthrough requests, and common MSW v2 API changes from v1.
Fix: Playwright Not Working — Test Timeout, Selector Not Found, or CI Headless Fails
How to fix Playwright test issues — locator strategies, auto-waiting, network mocking, flaky tests in CI, trace viewer debugging, and common headless browser setup problems.
Fix: Jest Setup File Not Working — setupFilesAfterFramework Not Running or Globals Not Applied
How to fix Jest setup file issues — setupFilesAfterFramework vs setupFiles, global mocks not applying, @testing-library/jest-dom matchers, module mocking in setup, and TypeScript setup files.