Skip to content

Fix: Vitest Setup Not Working (setupFiles, Mocks, and Global Config Issues)

FixDevs · (Updated: )

Part of:  JavaScript & TypeScript Errors

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 defined

Or 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 implementation

Or a testing library setup (like @testing-library/jest-dom) isn’t loaded:

Error: expect(...).toBeInTheDocument is not a function

Or 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. Even when you reuse vite.config.ts, the test block is read by a different loader and follows Vitest-specific resolution rules. That is why a project that builds and runs perfectly can still have broken tests.

The second source of confusion is the lifecycle. setupFiles runs once per worker process, before each test file’s imports are evaluated. globalSetup runs once before any worker spawns. beforeAll runs inside the test file itself. Hooks placed in the wrong layer either fire too early (before the modules they want to patch are even loaded) or too late (after the test has already cached the original module). When you put vi.mock() in setupFiles, Vitest does not hoist it the way it does inside a test file — so the import order is wrong and the real module wins.

Common failure causes:

  • globals: true not set — Vitest doesn’t expose describe, it, expect globally by default. You must either import them or enable globals: true.
  • setupFiles path is wrong — the path is relative to the config file, not the test file. A leading ./ is interpreted from the project root, not from src/.
  • vi.mock() in setupFiles doesn’t apply per-filevi.mock() in setupFiles hoists differently than in test files. Use setupFiles for environment setup, not for mocking modules.
  • Wrong test environment — browser APIs (window, document) require environment: 'jsdom' or environment: 'happy-dom', which is not the default (node).
  • TypeScript types not configureddescribe and expect exist at runtime but TypeScript reports them as undefined types because vitest/globals was never added to compilerOptions.types.
  • Leftover Jest configuration@types/jest, babel-jest, or a stale jest.config.js causes type conflicts and confuses tools like VS Code’s Jest extension into reporting phantom failures.
  • Workspace / monorepo path resolution — a vitest.config.ts in a workspace package does not inherit the root tsconfig paths, so @/utils resolves at compile time but breaks at test time.

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-dom

Fix 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') in setupFiles. Mocks set up in setupFiles are not automatically reset between tests and don’t get the per-test hoisting that vi.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(),
}));

In Production: Incident Lens

The dangerous failure mode here is not a noisy red test — it is a silently green one. When setupFiles does not run, the global polyfills, the matcher extensions, the mock service worker, and the database container setup all get skipped. The test file still imports, the assertions still execute, and the suite reports green. The bug that those assertions were supposed to catch sails straight into production.

That is why this is a production-incident topic even though Vitest itself never runs in production. The blast radius is “tests do not catch the bug that ships.” Every false-positive green test is a hole in your safety net, and the holes compound across sprints until something user-visible breaks and the post-mortem says “we had a test for that.”

Detection signals to wire up:

  • Test stability percentage tracked over time (CI dashboards in CircleCI, Buildkite Test Analytics, Datadog CI Visibility)
  • Mutation testing scores via Stryker — surviving mutants in covered code indicate weak assertions
  • Coverage delta per PR — a sudden drop in covered branches after a setup change is a red flag
  • A “canary test” that asserts globalThis.__SETUP_RAN__ was set by setupFiles. If it ever fails, the whole setup chain is broken
  • Suite duration trending too low — a setup that no longer initializes test containers or MSW will finish suspiciously fast

Recovery playbook:

  1. Revert the config changegit revert the commit that touched vitest.config.ts, tsconfig.json, or setupFiles. Restore the last green baseline.
  2. Add a sentinel test that fails loudly if setup did not run. Example: expect(globalThis.__SETUP_RAN__).toBe(true) with the flag set inside setupFiles.
  3. Re-run with --reporter=verbose to see which files Vitest actually loaded. Compare to expected.
  4. Diff coverage against the previous run — files that went from covered to uncovered point to which setup hook stopped firing.

Prevention:

  • Add a canary spec in every project that asserts setupFiles ran (a global flag set in setup, asserted in test).
  • Enable mutation testing in CI for high-stakes modules (auth, payments). A mutation score below 70 percent means assertions are too weak even if line coverage is 100 percent.
  • Pin Vitest version per project and require lockfile review for upgrades — minor versions occasionally change setupFiles semantics.
  • Run tests in CI with a non-developer Node version once per week to catch host-machine drift.

Still Not Working?

Run Vitest with --reporter=verbose to see exactly which files and setups load:

npx vitest --reporter=verbose

Check 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.ts

Verify your Node.js version. Vitest requires Node.js 18+:

node --version

Check 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/globals

Check for vmThreads vs threads pool mode. Vitest 1.x changed the default worker pool. If your tests rely on isolated module state, switch back to pool: 'forks' or pool: 'vmThreads'pool: 'threads' shares module state across files in some configurations, which can make mocks leak between test files. Set it explicitly in vitest.config.ts rather than relying on the default.

Check that source maps reach Vitest. When tests transpile through Vite, broken source maps cause stack traces to point at compiled output instead of source. Set sourcemap: true in your Vite build config and ensure vitest.config.ts does not override it with false. Without source maps, you waste hours debugging the wrong line of code.

Check for a stale watch-mode cache. node_modules/.vite and node_modules/.cache/vitest can hold transformed modules that no longer match the source. Delete them and rerun: rm -rf node_modules/.vite node_modules/.cache/vitest.

Verify transformMode for SSR vs web. If you test code that imports server-only modules (fs, node:path) inside a jsdom environment, Vitest may try to transform them as browser modules and fail. Add the package to test.server.deps.inline to force SSR transform.

For related testing issues, see Fix: jest.mock() Not Working, Fix: Jest Test Timeout, Fix: Jest Setup File Not Working, and Fix: React Testing Library Not Finding Element.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles