Jest 测试用例模拟 Promise.withResolvers 行为主要通过 jest.spyOn 拦截 Promise.withResolvers 方法,返回包含可控 resolve 和 reject 函数的对象。适用场景是需要测试异步流程控制逻辑,风险边界是运行环境需支持该 API 或提供 Polyfill。
先说结论:在 Jest 中模拟该行为的核心是接管 Promise 解析控制权,而非等待真实异步完成。
- 适合 异步状态需精确控制的单元测试
- 先看 运行环境是否支持 Promise.withResolvers
- 建议 使用 jest.spyOn 并在 afterEach 中恢复
命令速用版
以下代码片段展示了如何在 Jest 中快速模拟 Promise.withResolvers 返回结构,直接复制到测试文件中使用。
test('模拟 Promise.withResolvers', () => {
const mockResolver = {
promise: new Promise(() => {}),
resolve: jest.fn(),
reject: jest.fn()
};
const spy = jest.spyOn(Promise, 'withResolvers')
.mockImplementation(() => mockResolver);
// 执行业务代码
// myFunction();
// 验证
expect(Promise.withResolvers).toHaveBeenCalled();
// 清理
spy.mockRestore();
});为什么会这样
Promise.withResolvers 的核心作用是暴露 Promise 的 resolve 和 reject 方法,以便在外层控制异步状态。
在测试环境中,真实异步操作会导致测试不稳定或运行缓慢。通过模拟该方法,测试代码可以立即获取 resolve 和 reject 的引用,从而精确触发 pending、fulfilled 或 rejected 状态,无需等待真实网络请求或定时器。Jest 本身不提供针对该特定 API 的专用 mock 函数,因此需要手动构造返回对象并拦截原生方法。
分步处理
按以下步骤在 Jest 项目中实现模拟,确保测试隔离性和环境兼容性。
步骤 1:确认运行环境支持
检查 Node.js 版本,Promise.withResolvers 在 Node.js 22.0.0 及以上版本原生支持。如果使用较低版本,需确认是否引入了 Polyfill,否则测试会报 TypeError。
步骤 2:构造 Mock 对象
创建一个包含 promise、resolve、reject 属性的对象。resolve 和 reject 应使用 jest.fn() 包裹,以便后续验证调用情况。promise 属性可以是一个挂起的 Promise 实例。
步骤 3:拦截原生方法
使用 jest.spyOn(Promise, 'withResolvers') 拦截调用,并通过 mockImplementation 返回步骤 2 构造的对象。确保在 beforeEach 中设置,或在具体测试用例中设置。
步骤 4:恢复环境
在 afterEach 钩子或测试用例结束后,调用 spy.mockRestore() 恢复 Promise 原生行为,防止污染其他测试用例。
怎么验证是否生效
运行 Jest 测试命令后,通过以下指标确认模拟成功。
检查点 1:调用次数验证
断言 expect(Promise.withResolvers).toHaveBeenCalledTimes(1) 通过,说明拦截生效。
检查点 2:状态触发验证
手动调用 mock 对象中的 resolve 或 reject 函数,验证业务代码中的 then 或 catch 回调是否按预期执行。
检查点 3:日志输出
观察测试控制台无未捕获的 Promise rejection 警告,且测试状态为 PASS 而非 TIMEOUT。
常见坑
列出实施过程中容易出错的技术细节,提醒谨慎处理。
环境版本不匹配
本地 Node.js 版本过低导致 Promise.withResolvers 未定义,测试直接报错。建议在 CI 环境中锁定 Node.js 版本,或在测试入口文件添加兼容性检查。
忘记恢复 Spy
未在测试结束后调用 mockRestore,导致后续测试用例中 Promise 行为异常。务必将恢复逻辑放在 afterEach 中。
Promise 状态死锁
构造 mock 对象时,promise 属性若未正确处理,可能导致 await 永远等待。确保测试逻辑中会主动调用 resolve 或 reject,或使用 setImmediate 模拟异步。
常见问题
Node.js 多少版本支持 Promise.withResolvers?
Node.js 22.0.0 及以上版本原生支持该 API。
低于此版本需要确认项目是否引入了核心 JS Polyfill,否则在测试运行时会抛出 TypeError 错误。
Jest 有内置的 Promise.withResolvers 模拟工具吗?
Jest 官方文档中没有提供针对该特定方法的内置模拟工具。
开发者需要结合 jest.spyOn 和手动构造对象的方式来实现模拟,这是目前社区通用的处理方案。
模拟后如何触发 Promise 状态变更?
直接调用 mock 对象中暴露的 resolve 或 reject 函数即可触发状态变更。
这两个函数在 mock 实现中已被替换为 jest.fn(),调用它们会立即影响关联的 promise 实例状态。
参考来源
- MDN Web Docs: Promise.withResolvers() - https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers
- Jest Documentation: jest.spyOn(object, methodName) - https://jestjs.io/docs/jest-object#jestspyonobject-methodname
- Node.js Release Blog: v22.0.0 - https://nodejs.org/en/blog/release/v22.0.0