Why a Single jest.config Isn't Always Enough
A single Jest configuration assumes one testEnvironment, one set of transform rules, and one moduleNameMapper for the whole codebase, but many real projects need to run some tests in a jsdom browser-like environment for React components and others in the plain node environment for backend logic, or need entirely different setup files for unit versus integration suites. The projects option in jest.config.js lets you define an array of separate configurations, each scoped by its own testMatch or rootDir, that Jest runs together under one CLI invocation while keeping their settings independent.
Cricket analogy: Similar to a franchise like the Mumbai Indians running separate practice regimens for its batting unit and its bowling attack under one head coach, Jest's projects array runs separate configurations, each with its own environment and rules, under a single Jest invocation.
Defining the projects Array
Each entry in the projects array can either be a path to a directory containing its own jest.config.js, or an inline configuration object with a displayName, its own testEnvironment, and its own testMatch glob so Jest knows which files belong to which project. Giving each project a displayName, either a string or an object with name and color, makes it easier to distinguish output in the terminal when running node and jsdom suites side by side, since Jest prefixes each test result with that label.
Cricket analogy: Similar to labeling separate practice nets 'Pace Net' and 'Spin Net' with colored cones so players know which drills belong where, Jest's displayName with a name and color visually distinguishes output from different projects in the terminal.
// jest.config.js
module.exports = {
projects: [
{
displayName: { name: 'UNIT', color: 'blue' },
testEnvironment: 'node',
testMatch: ['<rootDir>/src/**/*.unit.test.js'],
},
{
displayName: { name: 'COMPONENT', color: 'magenta' },
testEnvironment: 'jsdom',
testMatch: ['<rootDir>/src/**/*.component.test.jsx'],
setupFilesAfterEach: ['<rootDir>/jest.setup.jsdom.js'],
},
{
displayName: { name: 'INTEGRATION', color: 'yellow' },
testEnvironment: 'node',
testMatch: ['<rootDir>/tests/integration/**/*.test.js'],
globalSetup: '<rootDir>/tests/integration/globalSetup.js',
},
],
};Shared Configuration and moduleNameMapper Duplication
A known limitation of the projects array is that top-level options like moduleNameMapper, transform, or collectCoverageFrom set outside the projects array are not automatically inherited by each project in many Jest versions, so common settings often need to be repeated inside each project entry or factored into a shared object spread into every project config. A common pattern is exporting a base configuration object from a shared file and using the spread operator, {...baseConfig, testEnvironment: 'jsdom'}, inside each project entry to avoid duplicating transform and moduleNameMapper rules three times.
Cricket analogy: Similar to a franchise writing one base fitness protocol document and having each squad, batting, bowling, fielding, copy and lightly adapt it rather than starting from scratch, a shared base Jest config object gets spread into each project entry to avoid duplicating rules like moduleNameMapper.
Since Jest 28, some top-level options such as coverage-related settings still apply globally regardless of the projects array, but per-project settings like testEnvironment, transform, and moduleNameMapper must be declared inside each project object; check the Jest version's changelog since this inheritance behavior has shifted across major releases.
Running and Filtering Specific Projects
Once configured, you can run every project in one invocation with a plain jest command, or scope a run to a subset using --selectProjects UNIT COMPONENT, which matches against each project's displayName, useful for a fast pre-commit hook that only runs unit tests while leaving slower integration projects for CI. Coverage collected across projects is merged into a single report by default, so a function tested only by an integration project still shows as covered in the aggregate summary even though no single project's individual run covers it in isolation.
Cricket analogy: Similar to a selector choosing to field just the T20 squad for a quick tour while leaving the Test squad at home for a longer series, --selectProjects lets you run just the UNIT project quickly while leaving slower INTEGRATION projects for a longer CI run.
Running jest --selectProjects locally as a pre-commit shortcut can create a false sense of safety if integration projects are always skipped locally and only run in CI, since a developer may push several commits before discovering an integration failure that a full local run would have caught earlier.
- The projects option lets a single Jest invocation run multiple independently configured test suites, such as node and jsdom environments.
- Each project entry can be a path to its own config file or an inline object with displayName, testEnvironment, and testMatch.
- displayName with a name and color labels each project's output distinctly in the terminal.
- Shared settings like moduleNameMapper often need to be factored into a base config object and spread into each project entry.
- --selectProjects filters a run to specific projects by displayName, useful for fast local pre-commit checks.
- Coverage across all projects is merged into a single aggregate report by default.
- Relying only on locally filtered project runs risks missing integration failures that surface only in the full CI run.
Practice what you learned
1. What is the primary purpose of Jest's projects configuration option?
2. What does displayName with a name and color option do?
3. Why might moduleNameMapper need to be repeated or shared via a spread pattern across project entries?
4. What does the --selectProjects CLI flag do?
5. How is coverage typically reported when using multiple projects?
Was this page helpful?
You May Also Like
Jest in Monorepos
Learn how to configure Jest to run efficiently across multiple packages in a monorepo, covering shared config, workspace-aware discovery, and CI caching.
Testing Node.js APIs and Express Routes
Learn how to test Express routes and Node.js API handlers with Jest and supertest, including mocking databases, external calls, and error-handling middleware.
Writing Custom Matchers
Learn how to extend Jest's expect with expect.extend() to write custom matchers that produce clearer, more expressive assertions and failure messages.