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

Jest单元测试中setTimeout()无法正常工作问题求助

Vue.js项目Jest单元测试:setTimeout逻辑中changeRoute未触发问题

用Jest对Vue.js项目进行单元测试时,测试setTimeout相关逻辑遇到问题:调用jest.advanceTimersByTime()推进指定时长后,changeRoute方法仍未被触发。

额外信息

  • 框架:Vue.js
  • 测试库:Jest

已尝试操作

  • 在设置任何定时器前调用jest.useFakeTimers()
  • 确认setTimeout的spy已正确配置
  • 确保在推进定时器前等待submitCall执行

相关代码

业务代码

async submitCall(error, data, message) {
  if (isBlank(error)) {
    this.successMessage = message;
    
    if (isBlank(data)) {
      await this.loadData(this.currentReportId);
      
      if (hasValue(this.successMessage)) {
        await new Promise((resolve) => {
          setTimeout(async () => {
            this.changeRoute({ name: 'report' });
            resolve();
          }, 5000);
        });
      }
    } else {
      await this.pageRedirection(data);
    }
  }
}

测试用例(原版本)

it('should call changeRoute after 5 seconds', async () => {
  jest.useFakeTimers();
  jest.spyOn(window, 'setTimeout');
  
  wrapper.vm.changeRoute = jest.fn();
  wrapper.vm.pageRedirection = jest.fn();

  expect(wrapper.vm.changeRoute).not.toHaveBeenCalled();

  const submitCallPromise = wrapper.vm.submitCall('', '', 'success message');

  jest.advanceTimersByTime(5000);

  await submitCallPromise;

  expect(wrapper.vm.changeRoute).toHaveBeenCalledTimes(1);
});

报错信息

模块聚合错误:断言失败,期望changeRoute方法被调用1次,但实际调用次数为0。

问题原因及解决方案

问题根源

  1. loadData未被mock:原代码中await this.loadData()如果是真实异步操作,会阻塞测试流程,导致后续定时器逻辑无法按预期执行。
  2. 假定时器推进后未处理微任务:Jest假定时器推进后,setTimeout回调会进入任务队列,但需手动触发微任务处理,才能让resolve()执行,进而完成await new Promise的等待。

修改后的测试用例

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

it('should call changeRoute after 5 seconds', async () => {
  jest.useFakeTimers();
  
  // Mock loadData,规避真实异步操作干扰
  wrapper.vm.loadData = jest.fn().mockResolvedValue();
  wrapper.vm.changeRoute = jest.fn();
  wrapper.vm.pageRedirection = jest.fn();

  expect(wrapper.vm.changeRoute).not.toHaveBeenCalled();

  // 调用submitCall,暂不await
  const submitCallPromise = wrapper.vm.submitCall('', '', 'success message');

  // 推进定时器到5秒后,触发setTimeout回调
  jest.advanceTimersByTime(5000);
  // 处理所有待处理微任务,让Promise的resolve执行
  await Promise.resolve();

  // 等待submitCall所有异步逻辑完成
  await submitCallPromise;

  expect(wrapper.vm.changeRoute).toHaveBeenCalledTimes(1);
});

额外优化点

  • 移除不必要的jest.spyOn(window, 'setTimeout'):若无需断言setTimeout的调用情况,可省略该spy,不影响假定时器功能。
  • 去掉setTimeout回调的async标记:如果changeRoute是同步方法,回调无需标记为async,避免不必要的异步嵌套。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 23:06:09