Jest 11.0 里程碑解析:Babel 无缝集成、Mock 调用提升机制与 Jasmine 2 默认化 📅 发布时间:2026/9/19 4:33:41 👁 浏览次数: Jest 11.0 里程碑解析Babel 无缝集成、Mock 调用提升机制与 Jasmine 2 默认化【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jestJest 11.0 是 Jest 历史上第一个采用 major 版本号发布的版本它标志着 Jest 从Facebook 内部工具向模块化、可配置的通用测试框架的关键转折。本篇基于仓库中官方发布博客 2016-04-12-jest-11.md 展开结合当前仓库中babel-jest、babel-plugin-jest-hoist、jest-config等包的源码实现深入讲解这一版本引入的 Babel 无缝集成、jest.mock系列 API 的提升hoisting机制、自动化 Mock 改进以及 Jasmine 2 默认化等核心变更。读完本文你将理解这些十年前的 API 设计如何在今天 Jest 的源码中继续延续并掌握jest.unmock/jest.mock/jest.fn/automock等配置与用法背后的底层原理。一、为什么 Jest 11.0 是第一个 major 版本Jest 11.0 的发布是 Jest 项目版本策略的一次正式转变。原文档明确说明Jest 当时已被 Facebook 工程师及其持续集成系统使用多年其成熟度早已远超一个 1.0 版本 所代表的含义因此团队决定跟随 React 团队的版本节奏直接切换到 major 版本号体系参见仓库中的版本迭代轨迹从 CHANGELOG.md 可以看到这一策略一直延续至今。对用户而言这次升级是无缝的如果正在使用 Jest 0.9 或 0.10过去几个月累积的所有变更都已被整合进 11.0无需额外迁移操作。这也是版本号只是里程碑而非破坏性变更信号这一理念的体现。二、Babel 集成与配置简化babel-jest 成为一等公民2.1 babel-jest 被收编进模块化仓库Jest 11.0 最核心的变化之一是babel-jest被正式收编进模块化组织的 Jest 仓库即当前仓库的 packages/babel-jest 目录并实现了与 Jest 的无缝集成。在此之后用户不再需要手动拼接 Babel 与 Jest 的桥接代码只需在 Jest 配置中声明transform即可完成代码转换。在今天的仓库中babel-jest的实现依然保留了这一设计的核心形态。packages/babel-jest/src/index.ts 中的createTransformer是核心入口它展示了 babel-jest 与 Jest 深度集成的几个关键点自动注入 jest preset通过require.resolve(babel-preset-jest)定位内置 preset并在合并 Babel 选项时默认追加presets: [..., jestPresetPath]除非显式传入excludeJestPreset: true见 index.ts。这意味着开箱即用的环境已经包含 Jest 所需的 Babel 配置caller 元数据向 Babel 传递caller: {name: babel-jest, supportsDynamicImport: false, ...}让 Babel 感知宿主环境能力index.tssourceMap 始终开启sourceMaps: both保证转换产物同时携带 sourcemap为后续的堆栈还原与覆盖率映射提供基础覆盖率插桩集成当transformOptions.instrument为真时自动追加babel-plugin-istanbul插件实现转换即插桩addIstanbulInstrumentation见 index.ts缓存键计算getCacheKey将 Babel 配置、源码文本、相对路径、NODE_ENV、BABEL_ENV、Node 版本等纳入 SHA-1 哈希确保配置或环境变化时缓存自动失效index.ts。从源码结构看这正是无缝集成的技术底座babel-jest 不只是外挂的转换器而是深度参与了 Jest 的缓存、覆盖率与能力探测机制。2.2 升级与新用户建议原文档建议从旧版本升级或新采用 Jest 的用户阅读 Getting Started 指南。该指南至今仍是了解 Jest 安装、配置与首个测试用例的标准入口其中关于transform与babel-jest的说明与 11.0 引入的集成思路一脉相承。三、Mock 调用提升Hoisting解决 ES2015 import 的先执行问题3.1 问题背景dontMock 与 import 的顺序冲突在 Jest 11.0 之前Jest 提供的是jest.dontMock这类 API用于在模块被require时阻止其被自动 mockjest.dontMock(LikeButton); const LikeButton require(LikeButton); // LikeButton 未被 mock然而当用户改用 ES2015 的import语法时问题出现了。按照规范import声明会被提升hoist到所在代码块的顶部。因此下面这段代码jest.dontMock(LikeButton); import LikeButton from LikeButton;实际执行顺序是import LikeButton from LikeButton; // 在 dontMock 调用之前执行 jest.dontMock(LikeButton);结果是即使显式调用了dontMockLikeButton依然会被 mock因为 import 先于 mock 控制语句执行。这是 ECMAScript 模块语义与运行时控制 mock这一旧模型之间的根本冲突。3.2 解决方案将 mock 控制调用提升到 import 之前Jest 11.0 与最新版 babel-jest 配合使用时引入了**调用提升hoisting**机制jest.unmock、jest.mock、jest.disableAutomock、jest.enableAutomock这些调用会在编译阶段被提升到所在代码块的顶部从而在 ES2015 import 语句之前执行jest.unmock(LikeButton); import LikeButton from LikeButton; // LikeButton 被正确地保持为真实实现3.3 源码级原理babel-plugin-jest-hoist这一机制在今天的仓库中由 packages/babel-plugin-jest-hoist 包实现其核心逻辑位于 src/index.ts。几个值得注意的实现细节可提升的 API 白名单FUNCTIONS表中定义了哪些jest.*调用可以被提升——mock1 个字符串/字面量参数或 2~3 个参数且第二个为内联函数、unmock、deepUnmock、disableAutomock、enableAutomock均要求字符串字面量或无参数见 index.tsout-of-scope 变量保护jest.mock的模块工厂函数不允许引用任何外部作用域变量除非该标识符属于ALLOWED_IDENTIFIERS如Array、Promise、require、expect、jest等内置对象、以mock前缀命名大小写不敏感或是 istanbul 的覆盖率变量cov。违反时会抛出ReferenceError并附带可读的错误提示index.ts 与 index.ts提升的执行时机插件在post阶段晚于 import 转换遍历语法树把标记为需要提升的调用语句从原位置移除并通过unshift插入到程序体/块级语句的最前面index.ts。这正是保证在 import 之前执行的落地方式jest 对象的识别既支持全局jest标识符也支持从jest/globals导入的jest甚至支持链式调用场景extractJestObjExprIfHoistable会递归解析被调用者见 index.ts。插件的快照测试如 hoistPlugin.nodejs20plus.test.ts.snap明确展示了jest.enableAutomock()/jest.disableAutomock()被转换为经由_getJestObj()getter 的调用并提升到文件顶部的过程读者可对照快照直观理解转换结果。从今天视角回看这套提升机制已成为 Jest Mock 体系的基石它保证了声明式 mock与ES 模块静态语义可以共存也让jest.mock成为 ESM/CJS 混用时代仍然可靠的核心 API。四、(Auto)Mocking 改进automock 配置、jest.mock 与 jest.fn4.1 全局 automock 开关Jest 11.0 之前自动化 mockautomocking是默认且难以关闭的行为这让许多只需要部分 mock 的团队感到困扰。11.0 新增了全局配置项automock布尔值可显式设置为false关闭自动 mock。在今天的仓库中这一配置被完整保留automock的默认值定义于 packages/jest-config/src/Defaults.tsautomock: false其语义说明见 packages/jest-config/src/Descriptions.tsAll imported modules in your tests should be mocked automatically。也就是说在默认关闭自动 mock 的前提下测试作者可以通过jest.mock/jest.unmock按需精确控制每个模块的 mock 行为——这正是 11.0 奠定的默认真实、按需 mock模型。4.2 jest.mock按测试粒度的手动 mock 工厂jest.mock允许为某个具体测试指定一个手动 mock 工厂。原文档给出了一个极简示例// 为假设的 sum 模块实现一个 mock jest.mock(sum, () { return (a, b) a b; }); const sum require(sum); sum(1, 4); // 5这个 API 与上一节提到的提升机制深度绑定jest.mock的调用被提升到 import 之前而模块工厂函数本身又在转换阶段被严格校验不允许引用外部变量从而保证 mock 工厂在任何模块加载前安全就绪。今天的jest.mock还支持{virtual: true}等第三参数选项并配套jest.requireActual读取真实实现但工厂函数 提升执行的核心契约从未改变。4.3 jest.fn轻量级 mock 函数为了简化创建一个可断言的函数这一高频需求11.0 引入了jest.fn// 创建 mock 函数 const mockFn jest.fn(() 42); mockFn(); // 42 expect(mockFn.calls.length).toBe(1);jest.fn的底层实现在今天的仓库中位于jest-mock包的ModuleMocker类对应源码 packages/jest-mock 目录其行为在 packages/jest-mock/src/tests/index.test.ts 中有大量覆盖例如通过moduleMocker.fn()创建、断言调用次数与参数calls属性在后续版本演进为mock.calls。从 11.0 开始jest.fn与jest.mock一起构成了 Jest手动 mock 三件套配置开关、模块级 mock、函数级 mock中函数级的一环。五、性能改进启动时间优化原文档提到Jest 团队在 11.0 发布前对性能进行了系统优化最显著的是启动时间startup time的改善。这背后与模块化改造直接相关将 babel-jest 等组件纳入统一仓库并缓存转换结果减少了测试进程初始化阶段的开销。从当前仓库的演进看启动性能的优化路径如 haste map 构建、转换缓存、worker 并行化在 packages/jest-haste-map 与 packages/jest-worker 等包中持续深化但 11.0 时代确立的性能是框架一级关注点这一原则延续至今。六、Jasmine 2 默认化更好的断言与测试运行器6.1 从 Jasmine 1 到 Jasmine 2Jest 开源时默认携带 Jasmine 1。由于 Jest 被设计为可与任意断言库协作社区通过 外部贡献 在 2015 年底加入了可选的 Jasmine 2 支持。Jasmine 2 提供了更好的性能与更完善的 APIFacebook 内部的所有 JavaScript 测试都迁移到了 Jasmine 2。Jest 11.0 正式将Jasmine 2 设为默认同时保留 Jasmine 1 作为可选项——通过testRunner配置项切换。在今天的仓库中testRunner的默认值已演进为jest-circus/runner见 packages/jest-config/src/Defaults.tsDescriptions.ts中其语义是 This option allows use of a custom test runnerDescriptions.ts。也就是说可插拔测试运行器这一架构决策由 11.0 确立并在之后被 circus当前默认运行器与 jest-jasmine2 等实现不断演进。仓库中的 packages/jest-circus 与 packages/jest-jasmine2 即为此架构下并存的两个实现。6.2 Jasmine 相关的配套改进围绕 Jasmine 211.0 还带来两处体验优化自定义匹配器失败信息增强为 Jest mock 函数提供的自定义匹配器其失败信息得到改进且现在同样适用于 Jasmine spies跳过测试的正确统计使用fit或fdescribe时的跳过测试现在会在测试运行结束时被正确报告不再静默丢失。七、其他变化watch 模式重写与 testEnvironment7.1 jest --watch 重写jest --watch命令在 11.0 中被完全重写行为发生重要变化默认只运行与改动文件相关的测试related tests避免每次保存都跑全量用例若希望在每次变更时运行全部测试使用jest --watchall。这一默认增量、可选全量的交互模型配合改进后的 verbose 日志输出与更友好的警告/错误消息显著提升了开发循环体验。从当前仓库的 e2e 测试如 watchModeOnlyFailed.test.ts、watchModePatterns.test.ts可以看出watch 模式的增量执行、失败优先、模式筛选等能力已成为 Jest 开发体验的核心组成部分。7.2 testEnvironment按项目类型定制运行环境11.0 新增testEnvironment配置项允许为测试定制运行环境。原文档给出的典型场景是构建 Node 服务时使用轻量的node环境替代默认的jsdom浏览器 DOM 模拟从而获得更快的启动速度与更贴近生产的环境。今天testEnvironment的默认值为jest-environment-node见 packages/jest-config/src/Defaults.ts配套的testEnvironmentOptions可向环境传递额外选项Defaults.ts。仓库中的 packages/jest-environment-node 与 packages/jest-environment-jsdom 即分别对应 Node 与浏览器两个官方环境e2e目录下的testEnvironment*.test.ts系列用例覆盖了环境切换、异步环境初始化等场景。7.3 文档与网站全面重写原文档最后提到网站与全部文档在 11.0 期间被完全重写。这一传统延续到今天当前仓库的 docs 目录包含从 GettingStarted.md、Configuration.md 到 MockFunctions.md、TimerMocks.md 等数十篇结构化文档构成了完整的学习与参考体系。八、Jest 11.0 的遗产回望与展望原文档结尾提到团队正在推进改进的 React (Native) 测试、增强的代码覆盖率支持并计划开源内部的多项目测试运行器——这些承诺在后来的版本中一一兑现如 React 测试库生态、v8 覆盖率提供者coverageProviderV8、--selectProjects多项目支持等。更重要的是Jest 11.0 确立的几项架构性决策至今仍是 Jest 的骨架模块化仓库组织从 11.0 起Jest 按功能拆分为多个包babel-jest、jest-config、jest-mock、jest-circus……这一组织方式在今天的 packages 目录中清晰可见Mock 调用提升机制jest.mock/jest.unmock等 API 的编译期提升由 babel-plugin-jest-hoist 持续维护是 ESM 时代 mock 可靠性的基石默认真实、按需 mock模型automock: false成为默认Defaults.tsjest.fn、jest.mock提供精准控制可插拔运行器与环境testRunner与testEnvironment的配置化设计让 Jest 得以承载不同断言库、不同宿主环境甚至后来的 ESM 原生支持。阅读 11.0 的发布说明就像在观看 Jest 设计哲学的第一现场今天我们在 JestObjectAPI.md、MockFunctionAPI.md 中熟悉的每个 API其设计动机都能回溯到 2016 年 4 月这篇公告所解决的问题——import 提升与 mock 顺序、自动 mock 的开关粒度、默认环境的选择、watch 模式的增量心智模型。理解这些历史约束能帮助你更准确地判断什么时候该用jest.mock、什么时候该关掉 automock、为什么测试代码里的 mock 调用必须写在顶层。这份为什么这样设计的洞察正是阅读版本发布文档对今天的开发者最大的价值。【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考