Nx 仓库导入后 Jest 测试集成指南:@nx/jest/plugin、jest.preset.js与常见问题修复
【免费下载链接】nxThe Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time.项目地址: https://gitcode.com/GitHub_Trending/nx/nx
在使用nx import将外部仓库合并进 Nx workspace 后,Jest 测试往往不会"开箱即用":测试 target 缺失、类型定义无法识别、transform 未配置、甚至package.json里的测试脚本被改坏。本文以 .opencode/skills/nx-import/references/JEST.md 为骨架,结合当前 Nx 仓库中 packages/jest 的插件源码、生成器实现与真实配置文件,系统讲解导入后 Jest 集成的完整链路——从插件推断机制、preset 与 tsconfig 配置,到依赖安装、Jest/Vitest 共存、CI 原子化与最终修复顺序。读完你可以独立完成任意仓库导入后的 Jest 测试修复与 CI 配置。
适用前提:本指南面向使用
nx import(参见 .opencode/skills/nx-import/SKILL.md)合并仓库的场景。关于基础的"Jest Preset Missing"修复(创建jest.preset.js并安装依赖),SKILL.md 中有简化说明,本文覆盖更深层的集成问题。
@nx/jest/plugin的工作原理
插件如何推断 test target
@nx/jest/plugin是一个 Nx 推断型插件(inference plugin)。它扫描工作区中符合**/jest.config.{cjs,mjs,js,cts,mts,ts}模式的文件,为每个配置存在的项目创建一个testtarget。这个 glob 定义在 packages/jest/src/plugins/plugin.ts:
const jestConfigGlob = '**/jest.config.{cjs,mjs,js,cts,mts,ts}';插件在createNodes中完成两件事:
- 并行加载所有 Jest 配置(通过
loadConfigFile,并优先查找tsconfig.spec.json、tsconfig.test.json、tsconfig.jest.json、tsconfig.json); - 为每个项目构建 targets:默认 target 名为
test,执行命令为jest,且会从 Jest 配置里解析出preset、setupFiles、moduleNameMapper等外部引用,自动推导任务inputs与outputs(缓存与增量测试的基础)。
注意插件的项目判定逻辑(checkIfConfigFileShouldBeProject):如果某个jest.config.*所在目录既没有package.json也没有project.json,插件不会为它创建项目;如果配置内容包含getJestProjectsAsync(),也会被跳过——因为该函数依赖项目图,会形成循环依赖。
插件配置选项
在nx.json中注册插件时可以传入选项:
{ "plugin": "@nx/jest/plugin", "options": { "targetName": "test" } }从源码JestPluginOptions(packages/jest/src/plugins/plugin.ts)可以看到完整选项:
| 选项 | 默认值 | 作用 |
|---|---|---|
targetName | "test" | 推断出的测试 target 名称 |
ciTargetName | 无 | 开启 CI 原子化(每文件一个 target)时使用的聚合 target 名,如test-ci |
ciGroupName | 由ciTargetName推导 | CI 原子化任务在 Nx Cloud 上的分组名 |
disableJestRuntime | false | 是否使用 Nx 自己的配置加载器与测试匹配器替代jest-config/jest-runtime;禁用后更快但可能在边界情况下不如 Jest 自身正确 |
useJestResolver | 与disableJestRuntime相反 | 是否用 Jest 的 resolver 解析 preset、transform、setup 文件等引用作为任务 inputs;能跟随 symlink、支持moduleDirectories/modulePaths |
normalizeOptions中targetName ??= 'test'(packages/jest/src/plugins/plugin.ts)保证未配置时默认为test。
npx nx add @nx/jest到底做了什么
npx nx add @nx/jest会执行 init 生成器(packages/jest/src/generators/init/init.ts),主要做两件事:
- 在
nx.json中注册@nx/jest/plugin——没有它,任何testtarget 都不会被推断出来; - 更新
namedInputs.production,将测试相关文件从生产文件集排除:
productionFileSet.push( '!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)', '!{projectRoot}/tsconfig.spec.json', '!{projectRoot}/jest.config.[jt]s', '!{projectRoot}/src/test-setup.[jt]s', '!{projectRoot}/test-setup.[jt]s' );此外,当工作区走显式 executor(@nx/jest:jest)路径时,init 生成器还会通过upsertTargetDefault写入 target 默认值:cache: true、inputs(default+^production+{workspaceRoot}/jest.preset.*)、options.passWithNoTests: true,以及ci配置(ci: true, codeCoverage: true)。
两个关键陷阱(原文档特别强调):
nx add @nx/jest不会创建jest.preset.js。该文件只在运行生成器(如@nx/jest:configuration)时才会生成。对于nx import的场景,你必须手动创建(见下一节)。- 反过来,如果你手动创建了
jest.preset.js却跳过了npx nx add @nx/jest,插件未注册,nx run PROJECT:test会报Cannot find target 'test'。两者缺一不可。
Jest Preset:共享配置的基石
根目录jest.preset.js
preset 提供了共享的 Jest 配置:测试匹配模式、ts-jest transform、resolver、jsdom 环境等。最小写法:
const nxPreset = require('@nx/jest/preset').default; module.exports = { ...nxPreset };当前 Nx 仓库根目录的真实 jest.preset.js 展示了在nxPreset基础上覆盖更多选项的写法:设置testTimeout、用@swc/jest替换默认的 ts-jest transform、覆盖testEnvironment为node、配置moduleNameMapper将 ESM-only 的@clack/prompts、ora、prettier等替换为 scripts/jest-mocks 下的 mock 实现,并通过setupFiles引入 scripts/unit-test-setup.js。这提示你:preset 是 workspace 级约定的最佳落点。
nxPreset的默认值
nxPreset定义在 packages/jest/preset/jest-preset.ts,核心默认值如下:
| 配置项 | 默认值 | 说明 |
|---|---|---|
testMatch | **/?(*.)+(spec|test).[jt]s?(x)(Jest 30 起支持?([mc])变体) | 匹配 spec/test 文件 |
resolver | @nx/jest/plugins/resolver | Nx 自定义 resolver,处理 monorepo 内跨包解析 |
moduleFileExtensions | ['ts', 'js', 'mjs', 'html'](Jest 30 起增加 mts/cts/cjs) | 支持 TypeScript 与 Angular 模板 |
coverageReporters | ['html'] | 覆盖率输出格式 |
transform | ts-jest,tsconfig: '<rootDir>/tsconfig.spec.json' | 默认用 ts-jest 转译 |
testEnvironment | jsdom | 注意:Nx preset 默认是 jsdom,纯 Node 项目需显式覆盖为node |
modulePathIgnorePatterns | ['<rootDir>/dist/', '<rootDir>/out-tsc/'] | 忽略构建产物 |
testEnvironmentOptions.customExportConditions | ['node', 'require', 'default'] | 强制 Jest 加载 CommonJS 代码,规避 ESM 兼容问题 |
项目级jest.config.ts
每个项目通过相对路径引用根 preset:
export default { displayName: 'my-lib', preset: '../../jest.preset.js', // project-specific overrides };preset路径是相对项目根目录到 workspace 根目录的。子目录导入时保留原始相对路径(如../../jest.preset.js),只要导入目标目录与源目录深度一致,该相对路径即可正确解析。当前仓库 e2e 测试 e2e/jest/src/jest.test.ts 中生成的配置同样使用了preset: "../../jest.preset.js"与["ts-jest", { tsconfig: "<rootDir>/tsconfig.spec.json" }]的 transform,可作为标准样板。
测试依赖安装
核心依赖(始终需要)
pnpm add -wD jest ts-jest @types/jest @nx/jest-wD表示安装到 workspace 根目录的 devDependencies。Jest 30 起还会依赖unrs-resolver,init 生成器中已通过acknowledgeBuildScripts处理其 postinstall(packages/jest/src/generators/init/init.ts)。
按环境区分
- DOM 测试(React、Vue、浏览器类库):需要
jest-environment-jsdom,因为nxPreset默认testEnvironment: 'jsdom'; - Node 测试(API、CLI):无需额外依赖。Jest 默认是
node环境,但 Nx preset 默认是jsdom——因此 Node 项目要在项目级jest.config.ts中显式覆盖testEnvironment: 'node'(仓库根 jest.preset.js 就是这么做的)。
React 测试
pnpm add -wD @testing-library/react @testing-library/jest-domReact + Babel(非 ts-jest transform)
部分 React 项目(老 Nx workspace、CRA 迁移项目)用 Babel 而非 ts-jest 做 JSX 转译:
pnpm add -wD babel-jest @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript何时需要:项目jest.config的transform使用的是babel-jest而不是ts-jest。判断方法就是检查jest.config.*中的 transform 段。
Vue 测试
pnpm add -wD @vue/test-utils注意:Vue 项目通常使用 Vitest 而非 Jest——详见 .opencode/skills/nx-import/references/VITE.md。
tsconfig.spec.json:测试文件的类型配置
每个 Jest 项目都需要一个包含测试文件的tsconfig.spec.json。标准模板:
{ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", "types": ["jest", "node"] }, "include": [ "jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts" ] }当前仓库 e2e 项目的真实配置 e2e/jest/tsconfig.spec.json 还额外展示了"isolatedModules": true、"rootDir": "."以及exclude: ["out-tsc"]的写法,include 数组覆盖了**/*.test.ts、**/*.spec.ts、JS/JSX 变体与jest.config.ts,可直接作为参考。
导入后常见问题:
- 缺少
"types": ["jest", "node"]→describe/it/expect全部无法识别,报Cannot find type definition file for 'jest'; - 缺少
"module": "commonjs"→ Jest 默认不支持 ESM(ts-jest 转译为 CJS),报Cannot use import statement outside a module; include数组缺少测试文件模式 → TypeScript 根本不会检查测试文件,类型错误被静默吞掉。
注意:types数组是显式列举而非自动发现,因此@types/jest必须安装且必须写入types;而 ts-jest 的 transform 会读取<rootDir>/tsconfig.spec.json,所以该文件路径与内容必须与 preset/项目配置对齐。
Jest 与 Vitest 共存
一个 workspace 可以同时存在两套测试体系:
- Jest:Next.js 应用、老 React 库、Node 库;
- Vitest:基于 Vite 的 React/Vue 应用与库。
@nx/jest/plugin与@nx/vite/plugin(后者推断 Vitest target)互不冲突——它们检测不同的配置文件(jest.config.*vsvite.config.*),见 packages/jest/src/plugins/plugin.ts 中的配置 glob 与 Vite 插件的vite.config.*匹配。
唯一注意点:两者默认 target 名都是test。如果某个项目异常地同时拥有两套配置文件,需要重命名其中一个:
{ "plugin": "@nx/jest/plugin", "options": { "targetName": "jest-test" } }另外,init 生成器在注册插件时默认会同时接受test、jest:test、jest-test三个 target 名(targetName: ['test', 'jest:test', 'jest-test']),为的就是与既有脚本或显式 target 平滑共存。
@testing-library/jest-dom:Jest 与 Vitest 的导入差异
从 Jest 迁移到 Vitest(或两者共存)的项目需要不同的导入路径。在test-setup.ts中:
Jest:
import '@testing-library/jest-dom';Vitest:
import '@testing-library/jest-dom/vitest';如果源仓库用的是 Jest 而目标 workspace 对该类项目使用 Vitest,记得更新导入路径,并把@testing-library/jest-dom加入 tsconfig 的types数组。
非 Nx 源仓库:package.json测试脚本重写问题
导入非 Nx 源仓库时,Nx 在 init 期间会重写package.json脚本,测试脚本尤其容易损坏:
"test": "jest"→"test": "nx test"(没有配置 executor 时形成循环调用);"test": "vitest run"→"test": "nx test run"(损坏——run变成了参数)。
修复:删除所有被重写的测试脚本。@nx/jest/plugin与@nx/vite/plugin会从配置文件推断 test target,package.json里不需要(也不应该有)重复的测试脚本。同理,如果源项目自带build/dev/start/lint脚本,Nx 插件会自动给推断 target 加前缀(如next:build、vite:build、eslint:lint)以避免冲突——要么接受前缀名(nx run app:next:build),要么在nx.json中重命名插件 target 名去掉前缀。
CI 原子化(Atomization)
@nx/jest/plugin支持按文件拆分测试任务,用于 CI 并行:
{ "plugin": "@nx/jest/plugin", "options": { "targetName": "test", "ciTargetName": "test-ci" } }配置后,插件为每个测试文件生成形如test-ci--src/lib/foo.spec.ts的独立 target,并创建一个名为test-ci的聚合 target(executor 为nx:noop,通过dependsOn转发参数和选项到各原子任务)。每个原子任务执行jest <relativePath>,继承cache、inputs、outputs。这些任务通过targetGroups分组(ciGroupName),可直接由 Nx Cloud 分发执行。
从源码看(packages/jest/src/plugins/plugin.ts),原子化还有几个细节:
- 通过 Jest 的
SearchSource/getTestPaths或 Nx 自己的globWithWorkspaceContext枚举测试文件,并按路径排序保证 target 名稳定; - 尊重
testPathIgnorePatterns,被忽略的文件不会生成原子任务; - 如果测试文件超出项目根目录会直接报错(防止错误配置静默放行);
- 聚合 target 的 metadata 中包含
nonAtomizedTarget: 'test',标记它对应的完整测试 target。
导入阶段用不到原子化,但它是导入后 CI 改造的直接收益点。
常见导入后问题清单
| # | 错误信息 | 根因 | 修复 |
|---|---|---|---|
| 1 | Cannot find target 'test' | @nx/jest/plugin未注册到nx.json | npx nx add @nx/jest或手动添加插件条目 |
| 2 | Cannot find module 'jest-preset' | workspace 根目录缺少jest.preset.js | 手动创建(见上文 preset 一节) |
| 3 | Cannot find type definition file for 'jest' | 缺少@types/jest,或tsconfig.spec.json没有"types": ["jest", "node"] | 安装依赖并修正 tsconfig |
| 4 | Cannot use import statement outside a module | ts-jest 未安装或未配置为 transform | 安装 ts-jest,检查jest.config.*的 transform 段 |
| 5 | 快照路径不匹配 | 导入后__snapshots__目录路径已固化 | 带--updateSnapshot跑一次测试重新生成 |
前两类问题与"Jest Preset Missing"基础修复的完整步骤同样记录在 .opencode/skills/nx-import/SKILL.md,本文不再重复。
推荐修复顺序
子目录导入(目标为 Nx 源)
npx nx add @nx/jest—— 在nx.json注册插件(不会创建jest.preset.js);- 手动创建
jest.preset.js(内容见上文); - 安装核心依赖:
pnpm add -wD jest jest-environment-jsdom ts-jest @types/jest; - 按框架安装测试依赖:React 装
@testing-library/react @testing-library/jest-dom,Vue 装@vue/test-utils; - 核对
tsconfig.spec.json包含"types": ["jest", "node"]; - 运行
nx run-many -t test验证。
整仓导入(非 Nx 源)
- 删除
package.json中被重写的测试脚本; npx nx add @nx/jest—— 注册插件(不创建 preset);- 手动创建
jest.preset.js; - 安装依赖(同上);
- 核对/修复
jest.config.*—— 确保preset路径指向根目录jest.preset.js; - 核对/修复
tsconfig.spec.json—— 补上types、module、include; - 运行
nx run-many -t test验证。
验证与进一步阅读
修复完成后,可以用 packages/jest/PLUGIN.md 中的命令做日常验证与单文件调试:
- 推断模式(
@nx/jest/plugin):nx test <project> -- --testPathPattern=<path/to/file.spec.ts>,按名称过滤用nx test <project> -- -t "pattern"; - 显式 executor 模式(
@nx/jest:jest):nx run <project>:test --testFile=<path/to/file.spec.ts>,按名称过滤用nx run <project>:test --testNamePattern="pattern"。
插件本身的行为由 packages/jest/src/plugins/plugin.spec.ts 等测试保障;如果想深入理解 preset 默认值,直接阅读 packages/jest/preset/jest-preset.ts;生成器的完整流程(含@nx/jest:configuration如何同时创建 preset 与项目配置)见 packages/jest/src/generators/configuration/configuration.ts。
【免费下载链接】nxThe Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time.项目地址: https://gitcode.com/GitHub_Trending/nx/nx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考