Skip to content

Fix: Jest Setup File Not Working — setupFilesAfterFramework Not Running or Globals Not Applied

FixDevs · (Updated: )

Part of:  JavaScript & TypeScript Errors

Quick Answer

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.

The Problem

Jest setup file code doesn’t run before tests:

// jest.setup.js
global.fetch = jest.fn();  // Should mock fetch globally

// test file
test('fetch is mocked', () => {
  expect(fetch).toBeDefined();  // ReferenceError: fetch is not defined
});

Or @testing-library/jest-dom matchers aren’t available:

// test file
expect(element).toBeInTheDocument();
// TypeError: expect(...).toBeInTheDocument is not a function

Or a module mock defined in the setup file doesn’t apply to test files:

// jest.setup.js
jest.mock('axios');  // Should mock axios in all tests

// test file
import axios from 'axios';
test('axios is mocked', () => {
  expect(jest.isMockFunction(axios.get)).toBe(true);  // false — not mocked
});

Why This Happens

Jest’s setup pipeline has multiple phases, each with a different scope and a different set of globals available. A setup file silently fails not because the code is wrong but because it lives in the wrong phase. The error you see — “fetch is not defined”, “toBeInTheDocument is not a function”, “axios.get is not a mock function” — does not point at the misconfigured key; Jest simply runs your tests as if the setup file did not exist.

The two configuration keys that matter are setupFiles and setupFilesAfterEach. They look similar but execute in completely different phases. setupFiles runs before the test framework module loads, so jest, expect, and beforeEach are undefined inside it. Use it for environment polyfills like TextEncoder or DOM patches that must exist before the test framework wires up its own internals. setupFilesAfterEach runs after the framework loads, so all the Jest globals are available. Use it for @testing-library/jest-dom and any code that calls jest.fn().

The third major source of confusion is that jest.mock() does not propagate from a setup file. Hoisted mocks are scoped to the file that calls jest.mock() — the call is moved to the top of that file by the Babel/SWC plugin, not into every test file. If you want a module to be mocked across every test, you need either a __mocks__ directory or a manual setup call in every test file.

Jest has two separate setup mechanisms with different timing and scope:

  • setupFiles — runs before the test framework (Jest/Jasmine) is installed. Use for polyfills and environment setup. Does NOT have access to jest globals like jest.fn() or expect.
  • setupFilesAfterFramework — the correct spelling is setupFilesAfterFramework. Runs after the framework is installed, so jest, expect, and matchers are available. This is where you add custom matchers, global mocks, and @testing-library/jest-dom.
  • Typo in config keysetupFilesAfterFramework vs setupTestFrameworkScriptFile (old Jest) vs setupFilesAfterFramework — a single typo means the file is silently ignored.
  • jest.mock() in setup files — module mocks registered with jest.mock() in setup files apply to the setup file’s module scope, not automatically to all test files.

Version History That Changes the Failure Mode

The setup file rules have changed considerably across Jest 25 through Jest 30. If you upgraded Jest and your previously-working setup file stopped firing, the cause is usually a renamed config key or a removed default.

  • Jest 25 (Jan 2020)setupTestFrameworkScriptFile (singular) was deprecated in favor of setupFilesAfterFramework (plural). Both still worked but the singular form printed a warning.
  • Jest 26 (May 2020) — final release that still accepted setupTestFrameworkScriptFile. Upgrade paths that skip from 25 to 27 frequently leave dead config keys behind.
  • Jest 27 (May 2021)jest-circus became the default test runner, replacing jest-jasmine2. The change is mostly transparent but setupTestFrameworkScriptFile was finally removed, and behavior around done callbacks tightened. setupFilesAfterFramework is now the only accepted key.
  • Jest 28 (Apr 2022)jest-environment-jsdom and jest-environment-node were moved out of the main package. Tests that relied on testEnvironment: 'jsdom' without the package being installed started failing at config-load time with “Cannot find module ‘jest-environment-jsdom’”. This affects setup files for browser-style tests directly.
  • Jest 29 (Aug 2022) — Node 12 support dropped; minimum Node version became 14. Snapshot format changed. pretty-format upgrade may produce diff noise on the first run after upgrade.
  • Jest 29.4+ — added --injectGlobals=false opt-out for environments that want to import globals explicitly. Setup files that relied on the implicit expect global break when this flag is enabled.
  • Jest 30 (alpha through 2025) — drops Node 14, raises minimum TypeScript and Babel versions, and tightens ESM handling. setupFilesAfterEach configurations involving native ESM modules need explicit transform rules.
  • Vitest comparison (2022 onward) — Vitest uses setupFiles (a single key combining both Jest phases) and supports both beforeAll/afterAll and synchronous code in the same file. Migrating Jest setup files to Vitest usually means combining them into one.

Run npx jest --version to confirm which release you’re on before debugging.

Fix 1: Use the Correct Config Keys

// jest.config.js
module.exports = {
  // Runs BEFORE the test framework — no jest.fn(), no expect
  // Use for: polyfills, global variable setup, environment patches
  setupFiles: [
    './jest.polyfills.js',      // e.g., TextEncoder, crypto polyfills
    './jest.env-setup.js',
  ],

  // Runs AFTER the framework — jest.fn(), expect, and custom matchers available
  // Use for: custom matchers, global mocks, testing-library setup
  setupFilesAfterFramework: [
    './jest.setup.js',
  ],

  testEnvironment: 'jsdom',  // 'node' for Node.js, 'jsdom' for browser-like
};

Note: In older versions of Jest (≤26), the key was setupTestFrameworkScriptFile (singular, deprecated). From Jest 27+, use setupFilesAfterFramework (plural).

TypeScript config:

// jest.config.ts
import type { Config } from 'jest';

const config: Config = {
  setupFilesAfterFramework: ['./jest.setup.ts'],
  testEnvironment: 'jsdom',
  transform: {
    '^.+\\.(ts|tsx)$': ['ts-jest', { tsconfig: './tsconfig.test.json' }],
  },
};

export default config;

Fix 2: Set Up @testing-library/jest-dom

npm install --save-dev @testing-library/jest-dom
// jest.setup.js
import '@testing-library/jest-dom';
// Or: require('@testing-library/jest-dom');

// Now all jest-dom matchers are available in all tests:
// toBeInTheDocument, toHaveTextContent, toBeVisible, toBeDisabled, etc.
// jest.config.js
module.exports = {
  setupFilesAfterFramework: ['./jest.setup.js'],
  // Or use the shorthand (testing-library provides a jest-dom setup file)
  setupFilesAfterFramework: ['@testing-library/jest-dom'],
};

TypeScript — add type declarations:

// jest.setup.ts
import '@testing-library/jest-dom';
// tsconfig.json — include jest-dom types
{
  "compilerOptions": {
    "types": ["jest", "@testing-library/jest-dom"]
  }
}

Fix 3: Global Mocks in Setup Files

For mocks that should apply to every test file, use setupFiles or setupFilesAfterFramework:

// jest.setup.js — global mocks available in all tests

// Mock fetch (not available in jsdom by default)
global.fetch = jest.fn();

// Reset all mocks before each test
beforeEach(() => {
  jest.clearAllMocks();
  // Or: jest.resetAllMocks() — also resets mock implementations
  // Or: jest.restoreAllMocks() — also restores spies
});

// Mock browser APIs not available in jsdom
global.ResizeObserver = jest.fn().mockImplementation(() => ({
  observe: jest.fn(),
  unobserve: jest.fn(),
  disconnect: jest.fn(),
}));

global.IntersectionObserver = jest.fn().mockImplementation(() => ({
  observe: jest.fn(),
  unobserve: jest.fn(),
  disconnect: jest.fn(),
  root: null,
  rootMargin: '',
  thresholds: [],
}));

// Mock matchMedia (not in jsdom)
Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: jest.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(),
    removeListener: jest.fn(),
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn(),
  })),
});

// Mock localStorage
const localStorageMock = (() => {
  let store: Record<string, string> = {};
  return {
    getItem: jest.fn((key: string) => store[key] ?? null),
    setItem: jest.fn((key: string, value: string) => { store[key] = value; }),
    removeItem: jest.fn((key: string) => { delete store[key]; }),
    clear: jest.fn(() => { store = {}; }),
  };
})();

Object.defineProperty(window, 'localStorage', { value: localStorageMock });

Fix 4: Module Mocks with mocks Directory

For automatic module mocking across all tests, use the __mocks__ directory:

project/
  __mocks__/
    axios.js          ← Auto-mocked for 'axios'
    ../utils/api.js   ← Manual mock for '../utils/api' (relative to __mocks__)
  src/
    __mocks__/
      myModule.js     ← Mock for './myModule' when used in src/
  node_modules/
    axios/
// __mocks__/axios.js — manual mock for the axios package
const axios = {
  get: jest.fn(() => Promise.resolve({ data: {} })),
  post: jest.fn(() => Promise.resolve({ data: {} })),
  put: jest.fn(() => Promise.resolve({ data: {} })),
  delete: jest.fn(() => Promise.resolve({ data: {} })),
  create: jest.fn(function() { return this; }),
  defaults: { headers: { common: {} } },
  interceptors: {
    request: { use: jest.fn(), eject: jest.fn() },
    response: { use: jest.fn(), eject: jest.fn() },
  },
};

module.exports = axios;
module.exports.default = axios;
// In test files — call jest.mock('axios') to activate the __mocks__/axios.js
import axios from 'axios';
jest.mock('axios');  // Uses __mocks__/axios.js automatically

test('uses mock axios', async () => {
  axios.get.mockResolvedValueOnce({ data: { id: 1, name: 'Alice' } });
  const result = await fetchUser(1);
  expect(result.name).toBe('Alice');
});

For user modules (not node_modules), mock must be explicit or automatic:

// src/__mocks__/api.ts
export const getUser = jest.fn();
export const createUser = jest.fn();

// In test — manual mock is used when jest.mock() is called
jest.mock('../api');  // Uses src/__mocks__/api.ts

Fix 5: Environment-Specific Setup

Different test environments (node vs browser) need different setups:

// jest.config.js — multiple projects with different environments
module.exports = {
  projects: [
    {
      displayName: 'dom',
      testEnvironment: 'jsdom',
      testMatch: ['**/*.browser.test.{ts,tsx}', '**/components/**/*.test.{ts,tsx}'],
      setupFilesAfterFramework: ['./jest.setup.browser.js'],
    },
    {
      displayName: 'node',
      testEnvironment: 'node',
      testMatch: ['**/*.test.ts', '!**/*.browser.test.ts'],
      setupFilesAfterFramework: ['./jest.setup.node.js'],
    },
  ],
};
// jest.setup.browser.js
import '@testing-library/jest-dom';
import { server } from './src/mocks/server';

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// jest.setup.node.js
// No DOM setup needed for node tests
process.env.NODE_ENV = 'test';
process.env.DATABASE_URL = 'postgresql://localhost:5432/testdb';

Fix 6: Verify Setup File Is Running

Add debug output to confirm the setup file actually executes:

// jest.setup.js
console.log('✓ Jest setup file loaded');

// Verify environment
console.log('Environment:', process.env.NODE_ENV);
console.log('Test environment:', process.env.JEST_WORKER_ID);
# Run with verbose output to see setup file execution
npx jest --verbose

# Run a single test to debug setup
npx jest path/to/test.test.js --verbose

# Show all console output (not silenced)
npx jest --verbose --no-coverage 2>&1

Check configuration is being read:

# Print the resolved Jest config
npx jest --showConfig | grep -A5 "setupFilesAfterFramework"

# Verify the setup file path resolves correctly
node -e "console.log(require.resolve('./jest.setup.js'))"

Fix 7: Reset State Between Tests

State leaks between tests cause flaky, order-dependent failures. Configure resets in the setup file so every test starts clean:

// jest.setup.js
import '@testing-library/jest-dom';

// Clear all mock calls but keep implementations
beforeEach(() => {
  jest.clearAllMocks();
});

// Reset DOM between tests (when using jsdom)
afterEach(() => {
  document.body.innerHTML = '';
  // Reset localStorage if you mocked it
  window.localStorage.clear?.();
});

// Restore real timers after tests that used fake timers
afterEach(() => {
  jest.useRealTimers();
});

The Jest config also exposes the same behavior as global options:

// jest.config.js
module.exports = {
  setupFilesAfterFramework: ['<rootDir>/jest.setup.js'],
  clearMocks: true,         // Auto-clear mock calls between tests
  resetMocks: false,        // Don't reset implementations (would break beforeAll mocks)
  restoreMocks: false,      // Don't auto-restore spies (manual control)
  testEnvironment: 'jsdom',
  testEnvironmentOptions: {
    customExportConditions: [''],  // Workaround for some packages on Node 18+
  },
};

Still Not Working?

ESM vs CommonJS conflict — if your project uses ES modules ("type": "module" in package.json) but your setup file uses require(), it fails. Use import syntax in setup files and configure transform in jest.config. From Jest 28+ you also need extensionsToTreatAsEsm and the --experimental-vm-modules Node flag for native ESM.

jest.config.js vs package.json "jest" conflict — Jest reads configuration from one source. If you have both jest.config.js and a "jest" key in package.json, jest.config.js takes precedence. Remove the duplicate.

Setup file path is relative to jest.config.js locationsetupFilesAfterFramework: ['./jest.setup.js'] resolves relative to where jest.config.js lives (usually the project root), not the test file. Use <rootDir>/jest.setup.js for explicit root-relative paths:

setupFilesAfterFramework: ['<rootDir>/jest.setup.js'],

Watch mode skips changed setup filesjest --watch re-runs only changed test files. If you edit jest.setup.js, watch mode may not pick it up because it’s not a test file. Press a to re-run all tests, or quit and restart watch mode.

projects config overrides root-level setup — when using the projects array for multi-package or multi-environment monorepos, root-level setupFilesAfterFramework is replaced, not merged, by each project’s config. Repeat the entry inside every project block that needs it.

@testing-library/jest-dom import path changed in v6 — the @testing-library/jest-dom/extend-expect entrypoint was removed. Use import '@testing-library/jest-dom' instead. If matchers silently stop working after a Jest or jest-dom upgrade, this is the first thing to check.

Migrating to Vitest — Vitest uses setupFiles (combining both Jest phases) and globals: true to enable Jest-style globals without imports. Most Jest setup files run unchanged once you flip the config keys.

For related Jest issues, see Fix: Jest Module Mock Not Working, Fix: Jest Async Test Timeout, Fix: Jest Coverage Not Collected, and Fix: Vitest Setup Not Working.

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