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

Angular单元测试中AutoScrollDirective内setTimeout回调未触发问题

问题根因

  • 测试执行时序错误:beforeEach 中提前执行了 fixture.detectChanges(),此时还未对 scrollIntoView 做Mock,且已经触发了指令的 ngAfterViewInit 完成了 setTimeout 注册,后续在测试用例中的操作已经错过执行时机。
  • Mock时机过晚:第一次变更检测触发指令生命周期注册setTimeout时,scrollIntoView还未被Mock,回调执行时调用的是原生方法,不会触发你在测试用例中后定义的Mock函数。
  • 手动调用测试组件的 ngAfterViewInit 无效:该方法是测试组件自身的生命周期钩子,调用它不会触发挂载在模板内元素上的指令的生命周期,指令的生命周期完全由Angular变更检测自动调度。
  • tick() 时间参数缺失:默认tick()只推进0ms时间,若要匹配指令默认100ms的延时,需要传入对应时间参数,或直接使用flush()清空所有待执行的定时器任务。

修正后的测试方案

最优实现(避免污染全局原型)

@Component({
  template: `<div appAutoScroll></div>`,
})
class TestAutoScrollDirectiveComponent {}

describe('AutoScrollDirective', () => {
  let fixture: ComponentFixture<TestAutoScrollDirectiveComponent>;
  let scrollIntoViewMock: jest.Mock;
  let originScrollIntoView: typeof HTMLElement.prototype.scrollIntoView;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [AutoScrollDirective, TestAutoScrollDirectiveComponent],
      imports: [BrowserDynamicTestingModule],
    });
    // 提前创建组件实例,但不触发变更检测
    fixture = TestBed.createComponent(TestAutoScrollDirectiveComponent);
    // 先完成Mock再触发生命周期
    originScrollIntoView = HTMLElement.prototype.scrollIntoView;
    scrollIntoViewMock = jest.fn();
    HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
  });

  // 测试结束后恢复原型,避免污染全局影响其他用例
  afterEach(() => {
    HTMLElement.prototype.scrollIntoView = originScrollIntoView;
  });

  it('should call scrollIntoView of the element with default delay', fakeAsync(() => {
    // 第一次触发变更检测,此时才会执行指令的ngAfterViewInit注册定时器
    fixture.detectChanges();
    // 推进100ms时间匹配默认delay,也可直接用flush()清空所有定时器
    tick(100);
    expect(scrollIntoViewMock).toHaveBeenCalledWith({ 
      behavior: 'smooth', 
      block: 'start', 
      inline: 'nearest' 
    });
  }));

  // 可扩展自定义delay的测试场景
  it('should call scrollIntoView with custom delay', fakeAsync(() => {
    // 修改测试组件模板传入自定义delay,后续逻辑同上述用例即可
  }));
});

优化点说明

  • 把第一次fixture.detectChanges()移到测试用例内,Mock完成后再触发变更检测,保证指令执行时Mock已经生效
  • 移除了无效的手动调用测试组件ngAfterViewInit的逻辑
  • 补充了测试完成后的原型恢复逻辑,避免全局污染影响其他测试用例
  • 不仅校验方法是否调用,还校验了传入的参数是否符合预期,测试覆盖更完整
  • 可直接扩展自定义delay的测试场景,复用性更强

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 05:36:05