跳至主内容

Jest 30:更快、更轻、更好

· 12 分钟阅读
Svyatoslav Zaytsev
Svyatoslav Zaytsev
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

今天我们很高兴地宣布 Jest 30 正式发布。本次版本包含了大量变更、修复和改进。尽管这是 Jest 有史以来最大的主要版本更新之一,但我们承认一个大版本更新间隔三年确实太长了。未来我们将致力于更频繁地发布主要版本,确保 Jest 在未来十年持续保持卓越。

如果你想跳过所有新闻直接开始,请运行 npm install jest@^30.0.0 并遵循迁移指南:从 Jest 29 升级到 30

有哪些新特性?

Jest 30 速度明显更快,内存占用更少,并带来了大量新功能。首先让我们看看破坏性变更:

破坏性变更

  • Jest 30 停止支持 Node 14、16、19 和 21

  • jest-environment-jsdom 从 jsdom 21 升级到了 26

  • 最低兼容 TypeScript 版本提升至 5.4

  • 移除了多个 expect 别名。eslint-plugin-jest 提供了自动修复工具,可自动升级代码库

  • 不可枚举的对象属性现在默认不会参与 toEqual 等对象匹配器的比较

  • Jest 现已默认支持 .mts.cts 文件

  • --testPathPattern 已重命名为 --testPathPatterns

  • Jest 现在能正确处理先被拒绝后被捕获的 Promise,避免误报测试失败

  • 我们对快照打印功能进行了多项改进,可能需要更新快照。Google 已弃用我们使用的 goo.gl 链接,虽然我们也不喜欢这种变动,但你必须更新所有快照

  • Jest 现在每个包都打包为单个文件,这提升了性能,但如果你开发了访问 Jest 内部机制的工具可能会受影响

以上只是部分重点内容,完整的破坏性变更列表请查阅 更新日志Jest 30 迁移指南

性能与内存优化

通过模块解析、内存使用和测试隔离等关键优化,Jest 30 实现了显著的性能提升。借助全新的 unrs-resolver,模块解析功能变得功能更丰富、更符合标准且速度更快。感谢 @JounQin 完成迁移工作。根据项目不同,你可能会观察到测试运行速度大幅提升且内存占用显著降低。例如某个包含客户端和服务端的大型 TypeScript 应用在其部分代码库中观察到测试运行速度提升 37%,内存使用降低 77%:

Jest 29Jest 30
Server tests~1350s / 7.8 GB max~850s / 1.8 GB max
Client tests~49s / 1.0 GB max~44s / 0.8 GB max

Jest 本身速度很快,但由于其测试隔离机制,运行缓慢的用户代码常常会加剧性能问题。当测试遗留了未关闭的句柄(如未清除的定时器或与其他服务的连接)时,可能导致 Jest 挂起或变慢。Jest 30 在检测和报告这类问题方面表现更佳,帮助开发者更轻松地定位和修复缓慢或有问题的测试。例如 Happo 的测试通过清理未关闭句柄并升级到 Jest 30,运行时间从 14 分钟缩短至 9 分钟,速度提升了 50%。

如果您正在使用将多个模块的导出合并到单个文件的文件(即"桶文件"),我们推荐使用 babel-jest-boostbabel-plugin-transform-barrelsno-barrel-file 等工具,避免为每个测试文件加载大量应用代码。这可将性能提升高达 100 倍。

测试文件之间的全局变量清理

Jest 通过在独立的 VM 上下文 中运行每个测试文件来实现隔离,为每个文件提供全新的全局环境。但如果您的代码未在测试文件后清理全局变量,可能导致内存泄漏并拖慢测试运行。Jest 30 新增了检测功能,会在测试运行后提醒您未正确清理的全局变量。

未来版本中,Jest 将在每次测试运行后自动清理全局变量。如果您在使用 Jest 30 时未收到未清理全局变量的警告,现在即可将清理模式设为 "on" 来完全启用此功能,从而显著节省内存并提升性能:

export default {
testEnvironmentOptions: {
globalsCleanup: 'on',
},
};

Jest 的默认设置为 globalsCleanup: 'soft'。如需禁用此功能可设为 off。若需保护特定全局对象不被清理(如共享工具或缓存),可使用 jest-util 将其标记为受保护:

import {protectProperties} from 'jest-util';

protectProperties(globalThis['my-property']);

感谢 @eyalroth 实现此功能!

新功能

增强的 ECMAScript 模块与 TypeScript 支持

使用 Jest 原生 ESM 时现已支持 import.meta.*file://。此外,您现在可直接使用 TypeScript 编写 Jest 配置文件,且原生支持 .mts.cts 文件而无需额外配置。若使用 Node 原生 TypeScript 类型剥离功能,我们不再加载 TypeScript 转换器剥离类型,从而加快测试运行。

间谍函数与 using 关键字

您现在可将 JavaScript 新的显式资源管理语法 (using) 与 Jest 间谍函数结合使用。如果环境支持,编写 using jest.spyOn(obj, 'method') 会在代码块结束时自动恢复间谍函数,无需手动清理。

test('logs a warning', () => {
using spy = jest.spyOn(console, 'warn');
doSomeThingWarnWorthy();
expect(spy).toHaveBeenCalled();
});

文档

expect.arrayOf

Jest 30 引入了新的非对称匹配器 expect.arrayOf,可验证数组中每个元素是否符合指定条件或类型。例如,您可验证数组所有元素均为数字:

expect(someArray).toEqual(expect.arrayOf(expect.any(Number)));

文档

新的 test.each 占位符:%$

使用 test.each 进行数据驱动测试时,现在可在测试标题中加入特殊占位符 %$ 来插入测试用例的编号。例如:

test.each(cases)('Case %$ works as expected', () => {});

该占位符 %$ 会被替换为测试的序列号。

文档

jest.advanceTimersToNextFrame()

@sinonjs/fake-timers 已升级至 v13,新增了 jest.advanceTimersToNextFrame() 方法。此函数可将所有待处理的 requestAnimationFrame 回调推进到下一帧边界,让您无需猜测毫秒时长即可轻松测试动画或依赖 requestAnimationFrame 的代码。

文档

可配置的测试重试机制

Jest 30 增强了 jest.retryTimes() 功能,新增选项让您能灵活控制重试行为。现在您可以指定重试延迟时间,或在测试失败后立即重试,无需等待整个测试套件完成:

// Retry failed tests up to 3 times, waiting 1 second between attempts:
jest.retryTimes(3, {waitBeforeRetry: 1000});

// Immediately retry without waiting for other tests to finish:
jest.retryTimes(3, {retryImmediately: true});

文档

jest.unstable_unmockModule()

Jest 30 新增实验性 API jest.unstable_unmockModule(),为模块解除模拟提供更精细的控制(尤其在使用原生 ESM 时)。

文档

jest.onGenerateMock(callback)

新增的 onGenerateMock 方法可注册回调函数,该回调会在 Jest 为模块生成模拟时触发。您可以在模拟对象返回测试环境前对其进行修改:

jest.onGenerateMock((modulePath, moduleMock) => {
if (modulePath.includes('Database')) {
moduleMock.connect = jest.fn().mockImplementation(() => {
console.log('Connected to mock DB');
});
}
return moduleMock;
});

文档

其他改进

自定义对象序列化

现在可以在自定义对象上定义静态属性 SERIALIZABLE_PROPERTIES,从而控制快照和错误消息中包含哪些属性,使输出更聚焦关键信息。

文档

异步配置支持

setupFilesAfterEnv 中列出的测试文件现在可导出异步函数或使用顶层 await,功能与 setupFiles 保持一致。

更多改进...

完整变更请参阅 更新日志

已知问题

jsdom 的规范兼容性调整可能导致部分用例失效,尤其是测试中模拟 window.location 的场景。Jest 现提供 @jest/environment-jsdom-abstract 包,方便您基于 jsdom 构建自定义测试环境。若需临时修补 jsdom,可应用jsdom 补丁到你的项目。未来我们可能提供更适配测试场景的 jsdom 替代方案。

未来规划

Jest 作为十年来最受欢迎的 JavaScript 测试框架,支撑着从小型库到全球最大代码库的各类项目。随着时间推移,我们积累了技术债务:部分功能仅少数用户使用,为减少破坏性变更我们长期维持兼容性。有些功能应通过扩展而非核心框架实现,部分特性甚至可能引导错误测试实践。同时核心团队人员变动导致版本迭代放缓。我们将通过以下方式改进:

  • 性能优化/技术债务:精简核心架构,移除非主流功能,聚焦核心价值

  • 稳定发布周期:建立可预测的版本迭代和废弃策略

  • 开放协作:公开开发流程,增加贡献入口,拓展贡献者群体

  • 大胆革新: 作为 Jest 团队,我们应当更加果敢。当前诸多因素制约着 Jest 发挥其全部潜力,是时候采取行动了。

好消息是,自我们构建这个具有清晰关注点分离的模块化框架以来,Jest 始终具备践行这些原则的坚实基础。现在正是付诸实践的时刻。更多细节即将揭晓!

致谢

没有社区的辛勤付出,就没有这次发布。衷心感谢大家!

@SimenB, @mrazauskas, @Connormiha, @liuxingbaoyu, @k-rajat19, @G-Rath, @charpeni, @dubzzz, @stekycz, @yinm, @lencioni, @phawxby, @lukeapage, @robhogan, @fisker, @k-rajat19, @connectdotz, @alesmenzel, @rickhanlonii, @mbelsky, @brunocabral88, @brandon-leapyear, @nicolo-ribaudo, @dj-stormtrooper, @eryue0220

特别向在此版本中首次为 Jest 贡献代码的开发者们致以谢意。感谢你们让 Jest 变得更好,惠及整个社区!

@eyalroth, @KhaledElmorsy, @mohammednumaan, @bensternthal, @BondarenkoAlex, @phryneas, @jayvdb, @brandonchinn178, @latin-1, @rmartine-ias, @fa93hws, @Dunqing, @gustav0d, @noritaka1166, @andreibereczki, @Dreamsorcerer, @satanTime, @icholy, @ecraig12345, @cgm-16, @sebastiancarlos, @dancer1325, @loganrosen, @zakingslayerv22, @dev-intj, @tez3998, @anbnyc, @pengqiseven, @thypon, @co63oc, @danielrentz, @jonasongg, @andrew-the-drawer, @phryneas, @hyperupcall, @tonyd33, @madcapnmckay, @dongwa, @gagan-bhullar-tech, @ikonst, @ZuBB, @jzaefferer, @brandonnorsworthy, @henny1105, @DmitryMakhnev, @askoufis, @RahulARanger, @Jon-Biz, @fynsta, @KonnorRogers, @BondarenkoAlex, @mouadhbb, @kemuridama, @Avi-E-Koenig, @davidroeca, @akwodkiewicz, @mukul-turing, @dnicolson, @colinacassidy, @ofekm97, @haze, @Vadimchesh, @peterdenham, @ShuZhong, @manoraj, @nicolo-ribaudo, @georgekaran, @MathieuFedrigo, @hkdobrev, @Germandrummer92, @CheadleCheadle, @notaphplover, @danbeam, @arescrimson, @yepitschunked, @JimminiKin, @DerTimonius, @vkml, @ginabethrussell, @jeremiah-snee-openx, @WillianAgostini, @casey-lentz, @faizanu94, @someone635, @rafaelrabelos, @RayBrokeSomething, @DaniAcu, @mattkubej, @tr1ckydev, @shresthasurav, @the-ress, @Mutesa-Cedric, @nolddor, @alexreardon, @Peeja, @verycosy, @mknight-atl, @maro1993, @Eric-Tyrrell22