
jest 中直接在 `array.prototype.map()` 内执行 `await expect(...)` 会导致断言失效,因 `map` 不等待 promise 完成;需改用 `promise.all` 或自定义异步映射函数确保所有断言同步执行并被 jest 正确捕获。
在单元测试中,我们常希望通过遍历枚举值(如 Status)批量验证相同逻辑——例如:当广告状态为 REJECTED、UNPUBLISH 等非 PENDING_REVISION 值时,service.approve() 必须抛出 BadRequestException。但若直接在 Object.keys(Status).map(async () => { ... }) 中调用 await expect(...).rejects.toThrow(),测试将静默通过,即使某些分支本应失败。
根本原因在于:
- map() 是同步方法,它会立即返回一个包含多个 Promise 的数组,而 Jest 的 expect 断言必须在当前测试作用域内被同步触发或显式等待;
- 若未 await 这些 Promise,Jest 在测试函数返回后即结束执行,此时异步断言尚未运行或已被忽略;
- 更严重的是,map 不具备并发控制能力,多个 mockAdsRepository.findOne 赋值会相互覆盖,导致实际执行时仅最后赋值的 mock 生效。
✅ 正确做法是:确保每个异步断言都被等待,并避免 mock 覆盖。推荐两种稳健方案:
方案一:使用 Promise.all + for...of(推荐,语义清晰、易调试)
it('should block approval of ads if status is not in pending revision', async () => {
const invalidStatuses = Object.values(Status).filter(
s => s !== Status.PENDING_REVISION,
);
// 逐个测试,互不干扰
await Promise.all(
invalidStatuses.map(async (status) => {
mockAdsRepository.findOne.mockResolvedValue({
...adsDto,
status,
});
await expect(service.approve(1, 1)).rejects.toThrow(BadRequestException);
}),
);
});✅ 优势:Promise.all 确保所有断言并发执行且全部完成;每次迭代独立设置 mock,无覆盖风险;错误堆栈可精准定位具体哪个 status 失败。
方案二:使用 for...of 循环(顺序执行,适合依赖顺序或调试)
it('should block approval of ads if status is not in pending revision', async () => {
const invalidStatuses = Object.values(Status).filter(
s => s !== Status.PENDING_REVISION,
);
for (const status of invalidStatuses) {
mockAdsRepository.findOne.mockResolvedValue({
...adsDto,
status,
});
await expect(service.approve(1, 1)).rejects.toThrow(BadRequestException);
}
});✅ 优势:逻辑线性、便于断点调试;mock 调用严格按序生效,避免竞态。
⚠️ 注意事项
- ❌ 避免使用 .map(async () => {}) 而不 await 其返回值——这等于“发射即忘”,Jest 无法感知其内部断言;
- ❌ 不要在循环中重复赋值 mockFn = jest.fn(...),应统一使用 .mockResolvedValue()(推荐)或 .mockImplementation(),确保 Jest Mock 系统正确追踪调用;
- ✅ 建议提取 invalidStatuses 到常量,提升可读性与可维护性;
- ✅ 若需测试“仅 PENDING_REVISION 允许通过”,可额外补充正向用例:
it('should allow approval only when status is PENDING_REVISION', async () => { mockAdsRepository.findOne.mockResolvedValue({ ...adsDto, status: Status.PENDING_REVISION, }); await expect(service.approve(1, 1)).resolves.toBeDefined(); // 或其他成功断言 });
通过以上重构,你不仅能解决断言丢失问题,还能让测试更健壮、可读、易扩展。








