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

如何在非Angular测试中使用zone.js处理事件队列替代硬编码延迟?

问题背景与需求

我们基于原生JS和浏览器API开发UI应用,在测试响应scroll/resize事件的复杂UI组件时,当前采用触发事件后等待硬编码时长(如100ms)的方式,代码示例如下:

it('does something', async () => {
  const [element, otherElement] = await someSetup();
  expect(element).isAlignedWith(otherElement);

  element.parentElement!.scrollTop -= 100;
  await delay(100); // 等待滚动事件处理完成

  expect(element).isStillAlignedWith(otherElement);
});

这种方式存在明显弊端:等待时长过短会导致测试不稳定,过长则拖慢整个测试套件。希望能像Angular的Fake Zones那样,不依赖Angular,直接清空任务队列中的所有事件后再执行断言。

解决方案

可以通过以下几种方式实现无需硬编码等待时长的测试,确保所有事件处理逻辑完成后再执行断言:

1. 递归刷新动画帧与微任务

浏览器的滚动/resize事件处理通常和重排、重绘绑定,requestAnimationFrame会在重绘前执行。通过递归刷新所有动画帧和微任务,能确保所有关联逻辑执行完毕:

function flushAllTasks() {
  return new Promise(resolve => {
    // 先执行所有微任务
    queueMicrotask(() => {
      // 递归触发所有requestAnimationFrame回调
      function rafLoop() {
        requestAnimationFrame(rafLoop);
      }
      rafLoop();
      // 等待最后一帧执行完成后结束
      requestAnimationFrame(resolve);
    });
  });
}

// 测试中替换硬编码delay
it('does something', async () => {
  const [element, otherElement] = await someSetup();
  expect(element).isAlignedWith(otherElement);

  element.parentElement!.scrollTop -= 100;
  await flushAllTasks();

  expect(element).isStillAlignedWith(otherElement);
});

2. 手动触发事件并刷新微任务

如果UI逻辑直接绑定在scroll/resize事件上,可以手动触发事件并同步执行回调,再刷新微任务队列:

it('does something', async () => {
  const [element, otherElement] = await someSetup();
  expect(element).isAlignedWith(otherElement);

  element.parentElement!.scrollTop -= 100;
  // 手动触发scroll事件,同步执行绑定的回调
  element.parentElement!.dispatchEvent(new Event('scroll', { bubbles: true, cancelable: true }));
  // 刷新所有微任务
  await Promise.resolve();

  expect(element).isStillAlignedWith(otherElement);
});

注意:这种方式仅适用于同步或微任务级别的事件回调,如果逻辑中包含setTimeout等宏任务,需要结合第一种方法处理。

3. 结合测试框架工具(如Jest)

如果使用Jest,可以模拟requestAnimationFrame并执行所有定时器,避免真实等待:

beforeEach(() => {
  // 模拟requestAnimationFrame,立即执行回调
  jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => {
    cb(Date.now());
    return 0;
  });
});

it('does something', async () => {
  const [element, otherElement] = await someSetup();
  expect(element).isAlignedWith(otherElement);

  element.parentElement!.scrollTop -= 100;
  // 执行所有定时器和模拟的动画帧回调
  jest.runAllTimers();
  // 刷新微任务队列
  await Promise.resolve();

  expect(element).isStillAlignedWith(otherElement);
});

内容的提问来源于stack exchange,提问作者C. V. Moore

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 07:12:13