You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jest测试中如何等待父函数内子异步函数执行完成?

解决Jest测试异步setTimeout中函数调用的问题

问题根源

你遇到的核心问题有两个:

  1. addNewBookSpy调用次数为0:因为setTimeout属于宏任务,即使启用了fake timers,若未正确推进计时器+等待微任务队列完成,测试会在异步函数执行前就结束。
  2. “测试完成后无法日志”报错:测试结束后仍有未处理的异步任务(setTimeout内的async函数返回的Promise)在执行,导致Jest抛出警告。

解决方案(无需重写父函数)

只需调整测试逻辑,正确使用Jest的fake timers并等待所有异步任务完成:

1. 确保fake timers的正确配置

在测试前后分别启用/恢复真实计时器,避免污染其他测试用例。

2. 推进计时器并等待微任务完成

setTimeout内的async函数会生成Promise,属于微任务队列。推进计时器后,必须等待微任务执行完毕才能断言调用次数。

代码示例

假设你的addMultipleBooks实现如下:

// 待测试函数
async function addNewBook(book) {
  // 实际异步逻辑,比如调用API
}

function addMultipleBooks(books) {
  books.forEach(book => {
    setTimeout(async () => {
      await addNewBook(book);
    }, 100);
  });
}

对应的测试代码:

describe('addMultipleBooks', () => {
  beforeEach(() => {
    // 启用fake timers,若Jest版本≥27,可尝试legacy模式解决兼容问题
    jest.useFakeTimers({ legacyFakeTimers: true });
    // 模拟addNewBook函数
    jest.spyOn(global, 'addNewBook').mockResolvedValue({ success: true });
  });

  afterEach(() => {
    // 恢复真实计时器
    jest.useRealTimers();
    // 清除所有mock
    jest.restoreAllMocks();
  });

  test('正确调用addNewBook对应次数', async () => {
    const testBooks = [{ id: 1, name: 'Book1' }, { id: 2, name: 'Book2' }];
    
    // 执行待测试函数
    addMultipleBooks(testBooks);
    
    // 推进所有待执行的计时器
    jest.runAllTimers();
    // 等待所有微任务(Promise)执行完成
    await Promise.resolve();
    
    // 断言调用次数和参数
    expect(addNewBook).toHaveBeenCalledTimes(testBooks.length);
    expect(addNewBook).toHaveBeenCalledWith(testBooks[0]);
    expect(addNewBook).toHaveBeenCalledWith(testBooks[1]);
  });
});

关键细节说明

  • 若使用Jest 27+的默认modern fake timers出现异常,切换到legacyFakeTimers模式通常能解决兼容性问题。
  • await Promise.resolve()的作用是让Jest等待微任务队列清空,确保setTimeout内的async函数完全执行完毕。
  • 必须在测试结束后恢复真实计时器,避免影响其他测试用例的时间相关逻辑。

内容的提问来源于stack exchange,提问作者S.L

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 09:57:19