最可靠的方式是直接在async函数内用try/catch捕获异常或让异常自然抛出至测试框架。async函数返回promise,未捕获异常会以rejected形式传递,测试时应避免吞错、用await调用并配合expect().rejects.tothrow()断言,mock需确保reject行为与真实场景一致。

直接在 async 函数内部用 try/catch 捕获异常,或让异常自然抛出到测试框架中,是验证异常冒泡行为最可靠的方式。关键不是“模拟冒泡”,而是确保异常不被意外吞掉,并能被测试断言捕获。
让异常真实抛出,由测试框架接管
async 函数返回 Promise,未捕获的异常会以 rejected Promise 形式向外传递。测试时应避免在被测函数里吞掉错误,而是让它冒泡到测试代码层面:
- 用
await调用被测 async 函数,此时未处理的异常会同步抛到当前 test 块中(如 Jest、Vitest) - 配合
expect(...).rejects.toThrow(...)断言 Promise 被 reject 且带指定错误 - 示例:
await expect(fetchUser(0)).rejects.toThrow('User not found')
测试不同层级的冒泡路径
异常可能从 await 的底层 Promise 逐层向上冒泡。需覆盖典型链路:
- 底层 Promise reject(如 fetch 失败)→ 中间 async 函数未 catch → 测试层 await 抛出
- 中间层主动 throw(如校验失败)→ 跳过 await → 直接 reject 当前函数返回的 Promise
- 注意:若中间层写了
try/catch却没 re-throw,冒泡就中断了——这正是要测的逻辑分支
避免常见陷阱
有些写法看似在测冒泡,实则绕过了关键路径:
- ❌ 不要用
.catch()在测试里手动处理,再断言 error —— 这测的是你自己的错误处理,不是冒泡本身 - ❌ 不要只检查返回的 Promise 是否
instanceof Promise—— 所有 async 函数都返回 Promise,和异常无关 - ✅ 正确做法:让测试框架的
expect().rejects或assert.rejects()(Node.js)直接观察 Promise 状态
需要 mock 时,控制 reject 行为
外部依赖(如 API 调用)通常需 mock,重点是让 mock 返回一个明确 reject 的 Promise:
- Jest 中用
mockRejectedValue(new Error('...')) - Vitest 中用
vi.mocked(fn).mockRejectedValue(new Error('...')) - 确保 mock 错误类型/消息与真实场景一致,否则断言可能通过但逻辑不真实











