Jest’s configuration system is a powerful beast, and jest.config.js is where you tame it. But what if you’re staring at a blank jest.config.js and don’t know where to start, or you’ve inherited a project with a cryptic config and want to understand it? This isn’t just about listing options; it’s about understanding why you’d use them and how they fit together.

Let’s see Jest in action with a simple setup. Imagine we have a small project:

my-app/
├── src/
│   └── math.js
└── __tests__/
    └── math.test.js

src/math.js:

export function add(a, b) {
  return a + b;
}

__tests__/math.test.js:

import { add } from '../src/math';

test('adds 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3);
});

If you run npx jest right now, it works. Jest has sensible defaults. But when your project grows, you’ll need to customize.

Now, let’s introduce jest.config.js and start building our mental model. This file is a Node.js module that exports a configuration object. Jest loads this file and applies the settings.

The most fundamental setting is testEnvironment. This dictates how the test environment is set up. For most web projects, you’ll want 'jsdom'. This simulates a browser environment, giving you access to document, window, and other DOM APIs. If you’re writing Node.js-specific code, 'node' is your friend, providing a clean Node.js runtime.

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
};

Running npx jest again with this config does the same thing, but now we’ve explicitly told Jest how to run our tests.

Next up: roots. This tells Jest where to look for test files and modules. By default, it looks in the current directory and its subdirectories. You can be more specific. If your tests are all in a tests folder, you might set:

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  roots: ['<rootDir>/tests'],
};

<rootDir> is a special token that resolves to the root directory of your project (where jest.config.js usually lives).

What about files that aren’t tests but are imported by tests? testMatch and testRegex are powerful for controlling which files Jest considers tests. testMatch uses glob patterns, while testRegex uses regular expressions. The default is often sufficient ('**/__tests__/**' or '**/?(*.)+(spec|test).[jt]s?(x)'), but you might have a specific naming convention.

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  roots: ['<rootDir>/tests'],
  // Example: Only run files named 'feature.test.js'
  testMatch: ['**/__tests__/**/*.feature.test.js'],
};

moduleNameMapper is a lifesaver for handling non-JavaScript assets like CSS, images, or even aliasing module paths. You map an import path (using regex) to a real path or a mock.

Let’s say you import CSS files in your React components:

// src/App.js (simplified)
import './App.css';
// ...

Jest doesn’t know what to do with .css files by default. You can map them to an empty module:

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  roots: ['<rootDir>/tests'],
  moduleNameMapper: {
    // Map all .css imports to an empty module
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
  },
};

identity-obj-proxy is a popular package that returns an object with the same keys as the imported value, which is often what CSS Modules rely on.

transform is another critical option. It tells Jest how to process files that aren’t plain JavaScript. For modern JavaScript (ES6+), TypeScript, or JSX, you’ll need a transformer. Babel is the most common.

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  roots: ['<rootDir>/tests'],
  moduleNameMapper: {
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
  },
  transform: {
    '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
  },
};

This configuration tells Jest to use babel-jest (which uses Babel under the hood) to transform any .js, .jsx, .ts, or .tsx files. You’d typically have a .babelrc or babel.config.js file to define your Babel presets and plugins.

The collectCoverage option is straightforward: set it to true to generate a coverage report. coverageDirectory specifies where this report goes, and coverageReporters defines the formats (e.g., lcov, text).

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  roots: ['<rootDir>/tests'],
  moduleNameMapper: {
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
  },
  transform: {
    '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
  },
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coverageReporters: ['lcov', 'text'],
};

When you run npx jest --coverage, you’ll see a coverage folder appear.

setupFilesAfterEnv is where you can run code after the test environment is set up but before each test runs. This is perfect for global setup, like importing custom Jest matchers or setting up mocks that should be available in every test.

// jest.config.js
module.exports = {
  // ... other options
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
};

And in jest.setup.js:

// jest.setup.js
// Example: Import a custom matcher
// import '@testing-library/jest-dom';
// Example: Global mock
// global.fetch = jest.fn(() => Promise.resolve({ json: () => ({}) }));

The moduleFileExtensions option lets you specify which file extensions Jest should look for when resolving modules. By default, it includes ['js', 'json', 'jsx', 'ts', 'tsx', 'node']. If you use .mjs files, you’d add it here.

// jest.config.js
module.exports = {
  // ... other options
  moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node', 'mjs'],
};

One of the most powerful but often overlooked aspects of Jest configuration is globals. This allows you to define global variables that are available in your test files. This can be useful for injecting configuration or feature flags into your tests without relying on module imports.

// jest.config.js
module.exports = {
  // ... other options
  globals: {
    __DEV__: process.env.NODE_ENV !== 'production',
    MY_API_URL: 'http://localhost:3000/api',
  },
};

Now, in any test file, you can access __DEV__ or MY_API_URL directly. This is particularly handy for testing code that behaves differently based on environment variables or global flags. You don’t need to mock process.env everywhere if you can define these globals once in your Jest config.

Understanding these options gives you fine-grained control over your testing environment, making Jest a robust tool for any JavaScript project.

The next hurdle you’ll likely encounter is handling complex asynchronous operations and mocking external dependencies effectively.

Want structured learning?

Take the full Jest course →