在 webpack 项目中使用 Jest:配置迁移、静态资源 Mock 与模块解析全指南
2026/9/19 23:03:20 网站建设 项目流程

在 webpack 项目中使用 Jest:配置迁移、静态资源 Mock 与模块解析全指南

【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest

Jest 完全可以与使用 webpack 管理资源、样式与编译流程的项目协同工作——本指南将完整演示如何把一份典型的 webpack 配置(loader、asset 规则、resolve别名与目录)逐项翻译为等价的 Jest 配置,并深入讲解moduleNameMappermoduleDirectoriesmoduleFileExtensionsmodulePaths与自定义transform的底层实现。读完本文,你将掌握在 webpack 工程中落地 Jest、优雅处理样式与图片等静态资源、以及正确配置模块查找路径的完整实战方案。

本文基于 Jest 30 官方文档 Webpack.md(与 docs/Webpack.md 内容一致)整理并扩充。

为什么 webpack 项目集成 Jest 会“特殊”

webpack 与其他工具相比之所以给测试带来独特挑战,在于它深度集成了应用本身:它负责管理样式表、图片和字体等资源,并支撑起庞大的"编译到 JavaScript"语言与工具生态。而 Jest 默认的模块解析与文件加载机制并不认识 CSS、图片等资源,因此需要一套显式的配置来"翻译"webpack 的能力。

好消息是:Jest 的大部分配置项都能与 webpack 的对应能力一一映射。核心思路是:

  • transform处理需要编译的 JavaScript/TypeScript 代码(默认走babel-jest);
  • moduleNameMapper把样式、图片等资源文件替换为 Mock 模块;
  • moduleDirectoriesmoduleFileExtensionsmodulePaths复刻 webpack 的模块查找逻辑;
  • moduleNameMapper的正则映射复刻 webpack 的resolve.alias

一个典型的 webpack 配置示例

下面是一份常见的 webpack 配置,它同时处理 JS/JSX 编译、CSS 样式、内联图片与字体资源,并配置了路径别名与自定义查找目录:

module.exports = { module: { rules: [ { test: /\.jsx?$/, exclude: ['node_modules'], use: ['babel-loader'], }, { test: /\.css$/, use: ['style-loader', 'css-loader'], }, { test: /\.gif$/, type: 'asset/inline', }, { test: /\.(ttf|eot|svg)$/, type: 'asset/resource', }, ], }, resolve: { alias: { config$: './configs/app-config.js', react: './vendor/react-master', }, extensions: ['.js', '.jsx'], modules: [ 'node_modules', 'bower_components', 'shared', '/shared/vendor/modules', ], }, };

这份配置中的每个部分,在 Jest 中都有对应的落点:

webpack 配置职责Jest 对应配置
module.rules(babel-loader)编译 JS/JSXtransform+babel-jest
module.rules(css/asset)加载样式与资源moduleNameMappertransform
resolve.extensions可省略扩展名的文件后缀moduleFileExtensions
resolve.modules模块查找目录moduleDirectories+modulePaths
resolve.alias模块路径别名moduleNameMapper(正则映射)

如果项目中的 JavaScript 文件由 Babel 转换,可安装babel-jest插件来启用 Babel 支持(参见 GettingStarted.md);非 Babel 的 JavaScript 转换则可用 Jest 的transform配置项处理。

babel-jest 到底做了什么

babel-jest是 Jest 官方提供的 Babel 转换器,实现位于 packages/babel-jest/src/index.ts。从源码可见几个关键事实:

  • 它通过createTransformer工厂创建符合 Jesttransform接口的转换器,实现了process/processAsync(同步/异步编译)与getCacheKey/getCacheKeyAsync(缓存键计算);
  • 默认会把babel-preset-jest追加进 Babel 的 presets(presets: [...(inputOptions.presets ?? []), ...(excludeJestPreset === true ? [] : [jestPresetPath])]),该 preset 负责jest.mock等调用的提升(hoisting)——因此文档特别提示:如果显式声明excludeJestPreset: true,会破坏jest.mock的提升机制;
  • 生成的缓存键(cache key)会综合 Babel 配置、源码内容、相对 rootDir 的路径、instrument标志、NODE_ENV/BABEL_ENV与 Node 版本等计算(见 getCacheKeyFromConfig)。这就是"修改了.babelrc之后需要jest --clearCache"的底层原因——Babel 配置变化会影响缓存键,但旧缓存可能未失效。

babel-jest的 README(packages/babel-jest/README.md)给出了最简洁的接入方式:安装babel-jest后它会自动用 Babel 编译 JavaScript;只有当你需要同时使用多个代码预处理器时,才需要显式在transform中声明它:

"transform": { "\\.[jt]sx?$": "babel-jest" },

还可以向babel-jest传递额外的 Babel 选项,例如:

"transform": { "\\.[jt]sx?$": ["babel-jest", { "extends": "./babel.config.js", "plugins": ["babel-plugin-transform-import-meta"] }] }

处理静态资源(Handling Static Assets)

webpack 能把 CSS、图片、字体等资源打包进应用,但这些文件对单元测试没有实际价值,因此标准做法是把它们 Mock 掉。通过moduleNameMapper,可以把匹配到的资源扩展名替换成指定的 Mock 模块:

module.exports = { moduleNameMapper: { '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/__mocks__/fileMock.js', '\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js', }, };

对应的两个 Mock 文件内容极简:

module.exports = {};
module.exports = 'test-file-stub';

即:样式模块返回空对象(因为测试中不关心样式),图片/字体等文件模块返回一个占位字符串'test-file-stub'。你可以根据 webpack 配置实际处理的文件类型,自由调整这里的正则表达式。

Mocking CSS Modules

如果项目使用 CSS Modules,直接返回空对象会导致styles.foobarundefined。更优雅的方案是使用 ES6 Proxy 库identity-obj-proxy来 Mock CSS Modules,安装方式:

npm install --save-dev identity-obj-proxy

然后在moduleNameMapper中把样式映射到该代理库:

module.exports = { moduleNameMapper: { '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/__mocks__/fileMock.js', '\\.(css|less)$': 'identity-obj-proxy', }, };

这样样式对象上的所有 className 查询都会原样返回,例如styles.foobar === 'foobar'。这对 React 的 Snapshot Testing(快照测试)非常有用——组件快照中可以稳定看到类名字符串。

用自定义 transformer 处理资源

如果moduleNameMapper无法满足需求,可以使用 Jest 的transform配置项来指定资源的转换方式。例如下面这个 transformer 返回文件 basename,使require('logo.jpg')返回'logo'

const path = require('path'); module.exports = { process(sourceText, sourcePath, options) { return { code: `module.exports = ${JSON.stringify(path.basename(sourcePath))};`, }; }, };
module.exports = { moduleNameMapper: { '\\.(css|less)$': 'identity-obj-proxy', }, transform: { '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/fileTransformer.js', }, };

注意:此例中process返回的是{ code }形式的对象,这也是当前 Jest 版本中转换器标准的返回值形态(babel-jestprocess同样返回{code, map},见 packages/babel-jest/src/index.ts)。

:::tip 保留默认 babel-jest 如果要在额外代码预处理器之外继续使用默认的babel-jest,请务必显式包含它,否则 JS/JSX 代码将不再走 Babel 编译:

"transform": { "\\.[jt]sx?$": "babel-jest", "\\.css$": "some-css-transformer" }

:::

配置 Jest 找到我们的文件

处理完"如何转换文件",还需要告诉 Jest"到哪里找文件"。webpack 的resolve.modulesresolve.extensions,在 Jest 中分别有直接的对应项:moduleDirectoriesmoduleFileExtensions

module.exports = { moduleFileExtensions: ['js', 'jsx'], moduleDirectories: ['node_modules', 'bower_components', 'shared'], moduleNameMapper: { '\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js', '\\.(gif|ttf|eot|svg)$': '<rootDir>/__mocks__/fileMock.js', }, };
  • moduleFileExtensions:模块可省略扩展名的后缀数组,对应 webpack 的resolve.extensions
  • moduleDirectories:从"发起 require 的模块所在位置"逐级向上递归搜索的目录名数组(见 Descriptions.ts),对应 webpack 的resolve.modules
  • moduleNameMapper:资源 Mock 继续生效。

:::note 关于<rootDir><rootDir>是 Jest 的特殊令牌,运行时会被替换为项目根目录。大多数情况下它就是package.json所在目录,除非你在配置中指定了自定义的rootDir。在 jest-config 的归一化逻辑中,moduleNameMapper的值会经过_replaceRootDirTags<rootDir>替换为真实的options.rootDir(见 normalize.ts),modulePaths/roots等数组项也会先replaceRootDirInPath再解析为绝对路径(见 normalize.ts)。 :::

modulePaths:对应 webpack 的 resolve.roots

webpack 的resolve.roots(设置NODE_PATH的替代方案)在 Jest 中的对应项是modulePaths

module.exports = { modulePaths: ['/shared/vendor/modules'], moduleFileExtensions: ['js', 'jsx'], moduleDirectories: ['node_modules', 'bower_components', 'shared'], moduleNameMapper: { '\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js', '\\.(gif|ttf|eot|svg)$': '<rootDir>/__mocks__/fileMock.js', }, };

modulePaths用于追加额外的绝对查找路径(如'/shared/vendor/modules'),从源码看它会与roots一起被解析为基于rootDir的绝对路径(见 normalize.ts)。

用 moduleNameMapper 复刻 resolve.alias

最后是 webpack 的resolve.alias。Jest 同样通过moduleNameMapper的正则映射来复刻:key 是匹配 import/require 路径的正则,value 是目标路径。

module.exports = { modulePaths: ['/shared/vendor/modules'], moduleFileExtensions: ['js', 'jsx'], moduleDirectories: ['node_modules', 'bower_components', 'shared'], moduleNameMapper: { '\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js', '\\.(gif|ttf|eot|svg)$': '<rootDir>/__mocks__/fileMock.js', '^react(.*)$': '<rootDir>/vendor/react-master$1', '^config$': '<rootDir>/configs/app-config.js', }, };

两个关键点:

  • '^react(.*)$': '<rootDir>/vendor/react-master$1'中,$1是正则捕获组引用,可把react及其子路径(如react-dom)一并重定向到 vendor 目录,等价于 webpack 配置中的react: './vendor/react-master'
  • '^config$': '<rootDir>/configs/app-config.js'精确匹配config模块,等价于 webpack 的config$: './configs/app-config.js'

注意正则写法:webpack 用config$$表示"以 config 结尾",Jest 的moduleNameMapper则用^config$表示"完整匹配 config 字符串",二者效果一致但语法习惯不同。

从 jest-config 源码看,moduleNameMapper的值支持"正则 → 模块名或模块名数组"的映射,并且每个 value 都会做<rootDir>标签替换(见 normalize.ts),官方描述为"从正则表达式到模块名或模块名数组的映射,用于用单个模块 stub 掉资源"(见 Descriptions.ts)。

让 Babel 与 Jest 协同:preset 与缓存

babel-jest之外,如果项目使用 Babel 编译 ES 新语法,还需要安装@babel/preset-env

npm install --save-dev @babel/preset-env

然后配置 Babel:

{ "presets": ["@babel/preset-env"] }

:::tip 清理缓存 Jest 会缓存文件以加速测试执行。如果你更新了.babelrc而 Jest 表现异常,尝试运行jest --clearCache清空缓存。 :::

其原理可参考上文对babel-jest缓存键的分析:缓存键由 Babel 配置、源码、环境变量等共同决定(getCacheKeyFromConfig),当.babelrc变更但缓存未失效时,就可能出现结果与预期不符的情况。

动态 import 的 Babel 配置

如果代码使用了动态导入(import('some-file.js').then(module => ...)),需要启用dynamic-import-node插件,并配合syntax-dynamic-import语法插件。推荐按环境区分配置,只在test环境启用该插件:

{ "presets": [["env", {"modules": false}]], "plugins": ["syntax-dynamic-import"], "env": { "test": { "plugins": ["dynamic-import-node"] } } }

这样在开发/构建环境保留原生动态 import 行为,而在 Jest 的 Node 测试环境中将其转换为 CommonJS 形式,避免 Node 无法直接解析 ESM 动态导入的问题。

小结:webpack 到 Jest 的配置映射速查表

webpack 配置Jest 配置说明
module.rules中的 JS 编译transform+babel-jest默认自动启用;多预处理器时需显式声明
module.rules中的资源加载moduleNameMapper→ Mock 文件样式返回{},文件返回'test-file-stub'
CSS ModulesmoduleNameMapperidentity-obj-proxyclassName 原样返回
自定义资源处理transform→ 自定义 transformer例如返回文件 basename
resolve.extensionsmoduleFileExtensions可省略扩展名
resolve.modulesmoduleDirectories递归向上查找的目录名
resolve.roots/NODE_PATHmodulePaths额外的绝对查找路径
resolve.aliasmoduleNameMapper正则映射^react(.*)$/$1等复刻别名

webpack 是一个复杂而灵活的工具,针对具体应用的特定需求,你可能需要进一步微调配置;但对大多数项目而言,Jest 的配置体系足以完整承接 webpack 的模块解析与资源处理能力。对于更复杂的 webpack 配置,可以进一步研究babel-plugin-webpack-loaders之类的生态工具,也可以参考 Jest 30 官方文档中的 Configuration.md 获取每个配置项的完整说明。想在一个 React + webpack 项目中亲自上手,可参照本文从一份真实 webpack 配置出发,逐步翻译出对应的 Jest 配置并运行验证。

【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询