无法测试MutationObserver observe()回调的问题排查
问题原因分析
你在测试中全局替换了MutationObserver为一个空实现的mock,但这个mock的observe方法只是一个空的jest.fn(),没有任何监听DOM变化并触发传入回调的逻辑。所以即使你修改了DOM元素的属性,也不会触发你定义的回调函数。
解决方案
下面提供两种可行的测试方案,根据你的测试需求选择:
方案1:完善MutationObserver的Mock实现(单元测试场景)
手动让mock的observe方法记录回调函数,在DOM变化时主动触发回调:
describe('MutationObserverDemoComponent', () => { let component: MutationObserverDemoComponent; let fixture: ComponentFixture<MutationObserverDemoComponent>; beforeEach(() => { TestBed.configureTestingModule({ declarations: [MutationObserverDemoComponent] }); fixture = TestBed.createComponent(MutationObserverDemoComponent); component = fixture.componentInstance; // 完善MutationObserver的mock,记录回调和目标元素 const mutationObserverMock = jest.fn<MutationObserver, [MutationCallback]>((callback) => { const observer = { observe: jest.fn((target, options) => { observer.target = target; observer.options = options; observer.callback = callback; }), disconnect: jest.fn(), takeRecords: jest.fn(), target: null, options: null, callback: null }; return observer; }); global.MutationObserver = mutationObserverMock; }); it('should observe DOM changes', () => { const target = fixture.debugElement.query(By.css('.mutation-div')).nativeElement; let observedMutations: MutationRecord[] = []; const observer = new MutationObserver((mutations: MutationRecord[]) => { observedMutations = observedMutations.concat(mutations); }); observer.observe(target, { attributes: true, childList: true, characterData: true }); target.setAttribute('data-mutation', 'testing'); // 手动触发mock的回调,传入模拟的MutationRecord const mockMutation: MutationRecord = { type: 'attributes', target, attributeName: 'data-mutation', oldValue: null, addedNodes: [], removedNodes: [], previousSibling: null, nextSibling: null, attributeNamespace: null, characterData: null, oldCharacterData: null }; observer.callback([mockMutation]); expect(observedMutations.length).toBe(1); expect(observedMutations[0].attributeName).toBe('data-mutation'); }); });
方案2:使用原生MutationObserver(集成测试场景)
Jest的jsdom环境支持原生MutationObserver,但要注意其回调是异步执行的,需要等待微任务队列完成:
describe('MutationObserverDemoComponent', () => { let component: MutationObserverDemoComponent; let fixture: ComponentFixture<MutationObserverDemoComponent>; beforeEach(() => { TestBed.configureTestingModule({ declarations: [MutationObserverDemoComponent] }); fixture = TestBed.createComponent(MutationObserverDemoComponent); component = fixture.componentInstance; // 移除mock,使用原生MutationObserver }); it('should observe DOM changes', async () => { const target = fixture.debugElement.query(By.css('.mutation-div')).nativeElement; let observedMutations: MutationRecord[] = []; const observer = new MutationObserver((mutations: MutationRecord[]) => { observedMutations = observedMutations.concat(mutations); }); observer.observe(target, { attributes: true, childList: true, characterData: true }); target.setAttribute('data-mutation', 'testing'); // 等待微任务执行,确保MutationObserver回调触发 await fixture.whenStable(); expect(observedMutations.length).toBeGreaterThan(0); expect(observedMutations[0].attributeName).toBe('data-mutation'); }); });
额外提示:测试组件内部的MutationObserver逻辑
如果你的组件内部初始化了MutationObserver,测试时应该聚焦于组件的行为而非手动创建Observer:
it('should initialize MutationObserver on init', () => { fixture.detectChanges(); // 触发ngOnInit // 验证MutationObserver是否被实例化 expect(global.MutationObserver).toHaveBeenCalled(); const observerInstance = global.MutationObserver.mock.results[0].value; expect(observerInstance.observe).toHaveBeenCalled(); }); it('should handle mutations correctly', () => { fixture.detectChanges(); // 获取组件内部传入的回调函数 const observerCallback = global.MutationObserver.mock.calls[0][0]; const mockMutations: MutationRecord[] = [{ type: 'attributes' } as MutationRecord]; // 触发回调,验证组件的处理逻辑 observerCallback(mockMutations); // 例如:expect(component.someProperty).toBe('expected-value'); });
内容的提问来源于stack exchange,提问作者Ricky
相关产品推荐
相关产品推荐

