
在 jest 单元测试中,若将 expect(...).rejects.tothrow() 置于 array.prototype.map() 等非 await-aware 的高阶函数内,因异步 promise 未被正确等待,会导致断言失效或测试“静默通过”。本文提供可靠、可复用的异步映射方案与最佳实践。
Jest 的 expect 断言必须在测试函数的同步执行流或显式 await 的异步链中被求值,才能触发失败时的错误抛出与测试终止。而原代码中使用 Object.keys(Status).map(async () => { ... }) 存在两个关键问题:
- map() 不等待 Promise:它仅返回一个 Promise 数组,但未调用 Promise.all() 或类似机制等待其完成;
- Jest 不会自动追踪“游离”的 Promise:这些未被 await 的 Promise 在测试函数退出后仍处于 pending 状态,Jest 视为已完成,导致断言失败被忽略。
✅ 正确做法是:显式并发执行所有断言,并统一等待全部完成。推荐使用以下简洁可靠的 mapAsync 工具函数(无需额外依赖):
// utils/test-helpers.ts export async function mapAsync( elements: readonly T[], mapAction: (item: T) => Promise , ): Promise { const results: R[] = []; for (const element of elements) { results.push(await mapAction(element)); } return results; }
? 注意:此处采用 for...of 循环逐个 await,确保每个 expect 断言按序执行且失败立即中断;若需完全并发(如性能敏感场景),可改用 Promise.all(elements.map(mapAction)),但需注意——Jest 推荐按序执行断言以便准确定位失败项。
在测试中应用如下:
it('should block approval of ads if status is not in pending revision', async () => {
// 遍历所有 Status 枚举值,排除期望成功的状态(PENDING_REVISION)
const invalidStatuses = Object.values(Status).filter(
s => s !== Status.PENDING_REVISION,
);
await mapAsync(invalidStatuses, async (status) => {
mockAdsRepository.findOne.mockResolvedValue({
...adsDto,
status,
});
await expect(service.approve(1, 1)).rejects.toThrow(BadRequestException);
});
});? 关键注意事项:
- ✅ 始终 await 每个含 expect 的异步操作,避免 Promise 泄漏;
- ✅ 使用 Object.values(Status) 而非 Object.keys(Status),避免枚举键名(如 '0', '1')误入测试逻辑;
- ⚠️ 切勿在 forEach、map、filter 等同步迭代器中直接 await —— 它们不处理 Promise 返回值;
- ✅ 若需测试“唯一成功路径”,建议单独编写一个正向测试用例,保持用例职责单一;
- ? 可配合 jest.clearAllMocks() 在 beforeEach 中重置 mock,提升测试隔离性。
通过结构化、可等待的异步映射,你既能消除循环断言失效风险,又能显著提升测试可维护性与可读性。










