Jest测试中如何带延迟模拟返回值并测试未决Promise?
Jest测试未决Promise的正确实现方案
原代码中的核心问题
- API调用错误:
mockResolvedValue接收的是直接resolved值,而非返回Promise的函数。要返回延迟resolve的Promise,需用mockImplementation或mockReturnValue。 - 拼写错误:
flushpromises应为flushPromises,toHavebeencalled应为toHaveBeenCalled(遵循驼峰命名规范)。 - 逻辑偏差:原测试意图是验证callout返回的Promise未决,但断言方向错误,误判callout是否被调用而非Promise状态。
假计时器方案修正代码
import callout from "../callout"; jest.mock("../callout", () => ({ default: jest.fn(), }), { virtual: true }); const { setImmediate } = require("timers"); function flushPromises() { return new Promise((resolve) => setImmediate(resolve)); } it("测试延迟Promise在推进部分时间后仍处于未决状态", async () => { jest.useFakeTimers(); // 模拟callout返回一个2秒后才resolve的Promise callout.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve("done"), 2000)) ); // 调用callout并获取返回的Promise const pendingPromise = callout(); // 推进20ms,远未达到resolve所需时间 jest.advanceTimersByTime(20); await flushPromises(); // 通过标志位验证Promise仍未决 let isResolved = false; pendingPromise.then(() => isResolved = true); expect(isResolved).toBe(false); // 推进足够时间触发Promise resolve jest.advanceTimersByTime(2000); await flushPromises(); expect(isResolved).toBe(true); expect(callout).toHaveBeenCalled(); // 恢复真实计时器,避免影响其他测试 jest.useRealTimers(); });
两种方案的完整实现
方案1:返回永久未决的空Promise
适合无需后续状态切换,仅需验证未决状态的场景:
it("测试空Promise的永久未决状态", async () => { // 返回一个永远不resolve/reject的空Promise callout.mockReturnValue(new Promise(() => {})); const pendingPromise = callout(); await flushPromises(); let isSettled = false; // 无论resolve还是reject都会标记为已完成 pendingPromise.then(() => isSettled = true).catch(() => isSettled = true); expect(isSettled).toBe(false); });
方案2:假计时器控制的延迟Promise
如修正后的代码所示,核心是通过模拟计时器控制Promise的resolve时机,实现"未决→已决"的状态切换验证,适合需要测试状态变化的场景。
关键注意事项
- 使用假计时器后,必须调用
jest.useRealTimers()恢复真实计时器,防止影响其他测试用例。 - 无法直接断言Promise对象的状态,需通过
then/catch回调设置标志位间接判断是否已决议。 flushPromises通过setImmediate清空微任务队列,确保同步代码执行完毕后再检查Promise状态。
内容的提问来源于stack exchange,提问作者DevLearner
相关产品推荐
相关产品推荐

