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: jest.mock() Not Working — Module Not Being Replaced in Tests
How to fix jest.mock() not intercepting module calls — why mocks are ignored, how to correctly mock ES modules, default exports, named exports, and fix hoisting issues in Jest tests.
Fix: TypeScript Decorators Not Working (experimentalDecorators)
How to fix TypeScript decorators not applying — experimentalDecorators not enabled, emitDecoratorMetadata missing, reflect-metadata not imported, and decorator ordering issues.
Fix: Next.js Middleware Not Running (middleware.ts Not Intercepting Requests)
How to fix Next.js middleware not executing — wrong file location, matcher config errors, middleware not intercepting API routes, and how to debug middleware execution in Next.js 13 and 14.
Fix: TypeScript isolatedModules Errors (const enum, type-only imports)
How to fix TypeScript isolatedModules errors — why const enum fails with Babel and Vite, how to replace const enum, fix re-exported types, and configure isolatedModules correctly for your build tool.