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

Angular指令中IntersectionObserver回调的测试覆盖问题

Angular指令中IntersectionObserver回调的测试覆盖问题

我看了你的指令和测试代码,问题出在你手动创建了一个新的MockIntersectionObserver实例,但指令内部自己创建的那个Observer你没拿到,所以你调用自己实例的回调根本不会触发指令里的逻辑。下面是调整后的测试方案,帮你覆盖到Observer的回调部分:

第一步:修正IntersectionObserver的Mock,捕获指令创建的实例

我们需要在Mock里保存每次创建的Observer实例,这样测试时能拿到指令内部生成的那个:

describe('ElementVisibilityHandlerDirective', () => {
  let component: TestHostComponent;
  let fixture: ComponentFixture<TestHostComponent>;
  let directive: ElementVisibilityHandlerDirective;
  let mockObserverInstance: MockIntersectionObserver;

  // Mock data
  const mockGroupEntityType: GroupEntityType = groupedTasks()[0] as GroupEntityType;
  const mockSingleEntityType: SingleEntityType = taskCardMock();

  class MockIntersectionObserver {
    observe: jasmine.Spy;
    unobserve: jasmine.Spy;
    disconnect: jasmine.Spy;
    callback: (entries: Array<IntersectionObserverEntry>) => void;

    constructor(callback: (entries: Array<IntersectionObserverEntry>) => void) {
      this.callback = callback;
      this.observe = jasmine.createSpy('observe');
      this.unobserve = jasmine.createSpy('unobserve');
      this.disconnect = jasmine.createSpy('disconnect');
      // 保存当前实例到外部变量,方便测试调用
      mockObserverInstance = this;
    }
  }

  beforeEach(async () => {
    (window as any).IntersectionObserver = MockIntersectionObserver;
    await TestBed.configureTestingModule({
      imports: [TestHostComponent, ElementVisibilityHandlerDirective],
    }).compileComponents();

    fixture = TestBed.createComponent(TestHostComponent);
    component = fixture.componentInstance;
    const directiveElement = fixture.debugElement.query(
      (debugEl) => debugEl.injector.get(ElementVisibilityHandlerDirective, null) !== null
    );
    directive = directiveElement.injector.get(ElementVisibilityHandlerDirective);
  });

第二步:测试Group Entity的Observer回调

现在我们可以触发指令创建Observer,然后调用它的回调来模拟元素相交:

describe('IntersectionObserver callback for Group Entities', () => {
  it('should emit visibleGroupedTasks when group entity intersects', async () => {
    // 1. 设置输入属性,触发指令里的effect创建Observer
    component.groupEntity = mockGroupEntityType;
    component.flagGroupEntityRecentlyAdded = true;
    fixture.detectChanges();

    // 2. 监听组件的回调方法,验证是否被调用
    spyOn(component, 'onVisibleGroupEntity').and.callThrough();

    // 3. 创建模拟的IntersectionObserverEntry,标记为相交状态
    const mockEntry: Partial<IntersectionObserverEntry> = {
      isIntersecting: true,
      target: fixture.nativeElement.querySelector('div')
    };

    // 4. 调用指令内部Observer的回调函数
    mockObserverInstance.callback([mockEntry as IntersectionObserverEntry]);

    // 5. 验证emit事件是否触发了组件的方法
    expect(component.onVisibleGroupEntity).toHaveBeenCalledWith(mockGroupEntityType);
    // 验证Observer是否调用了unobserve
    expect(mockObserverInstance.unobserve).toHaveBeenCalled();
  });
});

第三步:测试Single Entity的逻辑(同理)

按照同样的思路,你可以测试单个实体的场景:

describe('IntersectionObserver callback for Single Entities', () => {
  it('should emit visibleSingleTasks when single entity intersects', async () => {
    component.singleEntity = mockSingleEntityType;
    component.flagSingleEntityRecentlyAdded = true;
    fixture.detectChanges();

    spyOn(component, 'onVisibleSingleEntity').and.callThrough();

    const mockEntry: Partial<IntersectionObserverEntry> = {
      isIntersecting: true,
      target: fixture.nativeElement.querySelector('div')
    };

    mockObserverInstance.callback([mockEntry as IntersectionObserverEntry]);

    expect(component.onVisibleSingleEntity).toHaveBeenCalledWith(mockSingleEntityType);
    expect(mockObserverInstance.unobserve).toHaveBeenCalled();
  });
});

额外:测试NgOnDestroy的disconnect逻辑

别忘了测试销毁时的资源清理逻辑:

it('should disconnect observer on destroy', () => {
  component.groupEntity = mockGroupEntityType;
  component.flagGroupEntityRecentlyAdded = true;
  fixture.detectChanges();

  fixture.destroy();

  expect(mockObserverInstance.disconnect).toHaveBeenCalled();
});

关键要点说明

  • 我们的Mock会保存每次创建的Observer实例,这样就能拿到指令内部生成的那个,而不是自己新建一个无关的实例。
  • 必须先设置输入属性并调用fixture.detectChanges(),触发指令里的effect执行,进而调用placeObserver创建Observer。
  • 调用Observer的callback时,要传入包含isIntersecting: true的条目,这样才会触发emit逻辑。
  • 指令里用了zone.runOutsideAngular创建Observer,回调里用zone.run回到Angular上下文,测试里不需要额外处理,fixture.detectChanges()会自动处理变更检测。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 14:33:05