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
相关产品推荐
相关产品推荐

