Skip to content

Test a monorepo with projects

This guide runs the tests of several packages from the repository root with Jest’s projects option. Each package keeps its own config and base preset. The root config holds the watch plugins, because Jest reads watchPlugins from the root config only.

  1. Give each package its own Jest config with a base preset (not a -watch preset):

    packages/a/jest.config.mjs
    export default {
    preset: '@repobuddy/jest/presets/ts-esm',
    }
    packages/b/jest.config.cjs
    module.exports = {
    preset: '@repobuddy/jest/presets/js-cjs',
    }

    A -watch preset inside a project adds nothing. In a scratch run with Jest 30, jest --showConfig showed no watchPlugins when only a project used ts-esm-watch.

  2. Create the root config with the watch preset and the project list:

    jest.config.mjs
    export default {
    preset: '@repobuddy/jest/presets/watch',
    projects: ['<rootDir>/packages/*'],
    }
  3. Install the packages every project’s preset needs where Jest can resolve them from that project. Jest reports a missing one as Module @swc/jest in the transform option was not found, with the project’s <rootDir>.

  4. If any project uses an ESM preset, run Jest with NODE_OPTIONS=--experimental-vm-modules.

The presets decide two things when Jest loads them, and both use the directory Jest runs in, not the project’s rootDir:

  • The source folder (configSource()). Run from the root, a project whose tests live in source/ still gets roots: ['<rootDir>/src'] if the root has no source folder. Jest then fails with Directory .../packages/b/src in the roots[0] option was not found.
  • ESM or CommonJS for ts, ts-watch, jsdom-ts, and jsdom-ts-watch. Run from a root with "type": "module", a CommonJS project that uses ts gets the @swc/jest (ESM) config.

These results come from scratch runs with Jest 30. To avoid both cases:

  • Use the explicit presets (ts-esm, ts-cjs, jsdom-ts-esm, jsdom-ts-cjs) in projects.

  • Use the same source folder name in every project, or set roots and collectCoverageFrom in the project config with configs.configSource():

    packages/b/jest.config.mjs
    import { configs } from '@repobuddy/jest'
    export default {
    preset: '@repobuddy/jest/presets/js-cjs',
    ...configs.configSource('source'),
    }

    configSource('source') with an argument does no detection, and <rootDir> resolves per project.

Running Jest inside one package (cd packages/b && jest) detects that package’s folder and package.json.

jest.config.mjs
export default {
preset: '@repobuddy/jest/presets/watch',
projects: ['<rootDir>/packages/*'],
}
packages/a/jest.config.mjs
export default {
preset: '@repobuddy/jest/presets/ts-esm',
}
Terminal window
NODE_OPTIONS=--experimental-vm-modules jest

With two packages, the summary ends with Ran all test suites in 2 projects.. To confirm the watch plugins are set, run jest --showConfig and look for watchPlugins in globalConfig.