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

Jest异步测试:如何替代done()实现多回调完成后的断言?

Jest 多异步回调测试解决方案

你的测试用例存在两个核心问题:

  1. done()被提前触发(比如单个回调第一次执行就调用done,或者多个回调中任意一个执行就调用done),导致测试在所有逻辑完成前结束
  2. 同步断言toHaveBeenCalledTimes()在setTimeout回调执行前就运行,断言结果必然不符合预期

下面是针对性的解决方案:

一、模拟定时器,消除真实等待

Jest提供假定时器能力,可以同步执行所有待处理的异步定时器回调,不用等待真实时间,同时提升测试效率。需要先在测试前后配置假定时器:

beforeEach(() => {
  // 启用假定时器
  jest.useFakeTimers();
});

afterEach(() => {
  // 测试结束后恢复真实定时器,避免影响其他测试
  jest.useRealTimers();
});

二、计数器追踪所有回调完成

针对多回调或多次调用的场景,用计数器记录已执行的回调数量,当所有回调都执行完成后再结束测试。

修复示例1:单个回调被调用两次

it('测试单个回调被执行两次', (done) => {
  const args = 'some value';
  const callback = jest.fn((arg) => {
    expect(arg).toBe(args);
    // 当回调被调用第2次时,才调用done结束测试
    if (callback.mock.calls.length === 2) {
      done();
    }
  });

  subscribe('eventName', callback);
  publish('eventName', args);
  publish('eventName', args);

  // 同步执行所有待处理的setTimeout回调
  jest.runAllTimers();

  // 此时回调已执行,可同步断言调用次数
  expect(callback).toHaveBeenCalledTimes(2);
});

修复示例2:多个回调执行完成

it('测试所有回调在publish后执行', (done) => {
  const args = 'some value';
  const totalCallbacks = 2;
  let completedCount = 0;

  // 封装带计数的回调
  const createTrackedCallback = (originalCb) => (...cbArgs) => {
    originalCb(...cbArgs);
    completedCount++;
    // 所有回调都执行完成后结束测试
    if (completedCount === totalCallbacks) {
      done();
    }
  };

  const callback1 = createTrackedCallback(jest.fn((arg) => {
    expect(arg).toBe(args);
  }));
  const callback2 = createTrackedCallback(jest.fn((arg) => {
    expect(arg).toBe(args);
  }));

  subscribe('eventName', callback1);
  subscribe('eventName', callback2);
  publish('eventName', args);

  // 同步执行定时器回调
  jest.runAllTimers();

  // 同步断言调用次数
  expect(callback1).toHaveBeenCalledTimes(1);
  expect(callback2).toHaveBeenCalledTimes(1);
});

三、用Promise替代done(推荐)

如果不想用done,可以用Promise包装回调逻辑,结合async/await让测试更简洁:

it('测试单个回调被执行两次', async () => {
  const args = 'some value';
  let resolveTriggerCount = 0;
  let callback;

  // 创建Promise,当回调执行2次后resolve
  const callbackDone = new Promise((resolve) => {
    callback = jest.fn((arg) => {
      expect(arg).toBe(args);
      resolveTriggerCount++;
      if (resolveTriggerCount === 2) {
        resolve();
      }
    });
    subscribe('eventName', callback);
  });

  publish('eventName', args);
  publish('eventName', args);
  jest.runAllTimers();

  // 等待所有回调执行完成
  await callbackDone;

  // 断言最终调用次数
  expect(callback).toHaveBeenCalledTimes(2);
});

额外注意点

  • 原publish函数存在笔误:callback(...args)应该改为cb(...args)(forEach循环里的参数是cb)
  • 测试中要确保args变量已定义,否则会报错
  • 如果需要精确模拟时间(比如等待指定毫秒后执行),可以用jest.advanceTimersByTime(1000)替代jest.runAllTimers()

内容的提问来源于stack exchange,提问作者scazzy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 21:01:39