目标
快乐路径被测试,因为它容易想到;失败分支裸奔上线,因为它容易被忘掉。修法是机械的:盘点每个依赖、枚举失败模式、在测试中逐个强制触发、断言用户可观察的行为。白屏之所以发生,是因为从没人决定超时时该渲染什么,而测试会强迫把这个决定写进代码。
步骤要点
- 给目标模块列依赖失败模式清单:网关的超时/5xx/429/坏响应体、仓库的约束冲突/断连、缓存的未命中/宕机。
- 每种失败用 mockRejectedValue 确定性触发,跑进真实分支。
- 断言可观察契约而非异常类型:status=retryable、文案可重试、购物车未丢。
- 补“失败的失败”:日志系统也挂时用户仍能拿到可关联的 traceId。
- 用分支覆盖跟踪进展,未覆盖清单应收缩到真正不可达的防御代码。
排错提示
mock 形状漂移就做依赖的契约测试,或录制一次真实错误对象回放进 mock;错误分支不可达说明代码在内部吞错,先重构为返回状态或抛类型化错误才可断言;团队习惯只写快乐路径,就把失败清单变成评审检查项,加依赖不带失败测试的 PR 不予合并。
适用边界
单层的强制触发验证处理逻辑,真实错误形状仍需集成层故障注入;错误路径测试固化的是“预期的降级”,若没人决定超时的 UX,测试会响亮暴露这个空洞。
代码示例
it("renders retryable state when the gateway times out", async () => {
gateway.charge.mockRejectedValue(new TimeoutError("gateway"));
const state = await checkout(cart);
expect(state.status).toBe("retryable");
expect(state.message).toMatch(/try again/i);
});