async/await 本身不创建分支,但覆盖率工具将其视为 resolve/reject 两条路径;需通过 mock reject 和断言异常来覆盖错误处理分支,避免伪覆盖。

JavaScript 中 async/await 本身不会“隐式创建分支”,但测试覆盖率工具(如 Istanbul / nyc)常将 await 表达式识别为多个控制流路径,导致分支覆盖率下降——这其实是对 Promise 状态处理的缺失,而非语法分支本身。
理解覆盖率工具为何标记 await 为“未覆盖分支”
Istanbul 等工具基于 AST 分析,把 await expr 视为潜在的“两个出口”:Promise 成功(resolve)和失败(reject)。即使你没显式写 catch,底层仍存在隐式 rejection 路径。若测试中所有 await 都只走 resolve,reject 分支就显示为未覆盖。
- 不是代码有 bug,而是测试未触发 Promise 拒绝场景
- 常见于 mock 返回 resolve-only Promise 的测试用例
- 尤其在调用外部 API、数据库或定时器等异步操作时易被忽略
确保 reject 路径被测试覆盖
为提升分支覆盖率,需主动构造 Promise rejection 场景,并验证错误处理逻辑是否执行:
- 使用
jest.mock()或sinon.stub()让被 await 的函数返回Promise.reject(new Error(...)) - 在测试中用
expect(...).rejects.toThrow(...)或await expect(...).rejects...断言异常 - 确保
try/catch块中的catch分支被执行(例如打日志、抛新错、降级处理)
示例:
立即学习“Java免费学习笔记(深入)”;
// 被测函数
async function fetchUser(id) {
try {
const res = await api.getUser(id);
return res.data;
} catch (err) {
console.error("Fetch failed:", err.message);
throw new Error("User not found");
}
}
// 测试用例(覆盖 reject 分支)
test("fetchUser rejects and logs error", async () => {
api.getUser.mockRejectedValue(new Error("Network timeout"));
await expect(fetchUser(123)).rejects.toThrow("User not found");
expect(console.error).toHaveBeenCalledWith("Fetch failed:", "Network timeout");
});
避免无意义的“伪覆盖”写法
不要为凑覆盖率而添加不具业务意义的空 catch 或忽略错误:
- ❌
await fn().catch(() => {});—— 掩盖问题,降低可维护性 - ❌ 在测试中仅调用
await fn();却不校验 reject 行为 - ✅ 明确设计错误策略:重试、fallback、用户提示、上报监控
- ✅ 在测试中模拟每种预期失败原因(网络断开、404、500、超时)
配合工具配置合理忽略(谨慎使用)
若确认某处 await 绝对不会 reject(如纯内存计算的 async 函数),可通过注释临时忽略分支覆盖:
// istanbul ignore next const data = await Promise.resolve(transform(input));
但更推荐:用类型或文档说明其确定性,而非绕过检查。长期依赖 ignore 容易掩盖真实风险。


















