Fix: Jest Setup File Not Working — setupFilesAfterFramework Not Running or Globals Not Applied
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 functionOr 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 tojestglobals likejest.fn()orexpect.setupFilesAfterFramework— the correct spelling issetupFilesAfterFramework. Runs after the framework is installed, sojest,expect, and matchers are available. This is where you add custom matchers, global mocks, and@testing-library/jest-dom.- Typo in config key —
setupFilesAfterFrameworkvssetupTestFrameworkScriptFile(old Jest) vssetupFilesAfterFramework— a single typo means the file is silently ignored. jest.mock()in setup files — module mocks registered withjest.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 ofsetupFilesAfterFramework(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-circusbecame the default test runner, replacingjest-jasmine2. The change is mostly transparent butsetupTestFrameworkScriptFilewas finally removed, and behavior arounddonecallbacks tightened.setupFilesAfterFrameworkis now the only accepted key. - Jest 28 (Apr 2022) —
jest-environment-jsdomandjest-environment-nodewere moved out of the main package. Tests that relied ontestEnvironment: '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-formatupgrade may produce diff noise on the first run after upgrade. - Jest 29.4+ — added
--injectGlobals=falseopt-out for environments that want to import globals explicitly. Setup files that relied on the implicitexpectglobal break when this flag is enabled. - Jest 30 (alpha through 2025) — drops Node 14, raises minimum TypeScript and Babel versions, and tightens ESM handling.
setupFilesAfterEachconfigurations involving native ESM modules need explicittransformrules. - Vitest comparison (2022 onward) — Vitest uses
setupFiles(a single key combining both Jest phases) and supports bothbeforeAll/afterAlland 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+, usesetupFilesAfterFramework(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.tsFix 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>&1Check 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 location — setupFilesAfterFramework: ['./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 files — jest --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.
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 Async Test Timeout — Exceeded 5000ms or Test Never Resolves
How to fix Jest async test timeouts — missing await, unresolved Promises, done callback misuse, global timeout configuration, fake timers, and async setup/teardown issues.
Fix: Jest Coverage Not Collected — Files Missing from Coverage Report
How to fix Jest coverage not collecting all files — collectCoverageFrom config, coverage thresholds, Istanbul ignore comments, ts-jest setup, and Babel transform issues.
Fix: Jest Fake Timers Not Working — setTimeout and setInterval Not Advancing
How to fix Jest fake timers not working — useFakeTimers setup, runAllTimers vs advanceTimersByTime, async timers, React testing with act(), and common timer test mistakes.
Fix: Jest Module Mock Not Working — jest.mock() Has No Effect
How to fix Jest module mocks not working — hoisting behavior, ES module mocks, factory functions, mockReturnValue vs implementation, and clearing mocks between tests.