Angular单元测试:用Jest Mock自定义Dialog及解决未定义报错
Angular Jest测试:Mock Dialog组件解决
openModalDialog未定义错误 问题描述
组件方法showMessagesList会读取localStorage中的isEditMode值,当值为'true'时调用modalDialog.openModalDialog()打开弹窗。但在单元测试中:
- 第一个测试用例设置
isEditMode='true'并调用方法后,触发异常Cannot read property 'openModalDialog' of undefined - 第二个测试用例因Mock了方法本身未报错,但未真实覆盖分支逻辑
异常栈:
TypeError: Cannot read property 'openModalDialog' of undefined 49 | this.isEdit = localStorage.getItem('isEditMode').toString() 50 | if (this.isEdit === 'true') { > 51 | this.modalDialog.openModalDialog() | ^
问题根源
当前TestBed配置中,ModalDialogComponent的provider使用了空对象useValue: {},该对象没有openModalDialog方法,导致代码执行时调用未定义的属性。
解决方案
1. 正确Mock Dialog组件实例
修改TestBed的providers配置,给ModalDialogComponent提供包含mock方法的对象:
providers: [ { provide: Window, useValue: window }, // 替换空对象为带mock方法的实例 { provide: ModalDialogComponent, useValue: { openModalDialog: jest.fn() } }, ],
2. 修复第一个测试用例
让测试真实执行方法逻辑,并断言弹窗方法被调用:
it('should call openModalDialog when isEditMode is true', () => { localStorage.setItem('isEditMode', 'true') // 获取mock的Dialog实例 const modalDialog = TestBed.inject(ModalDialogComponent); component.showMessagesList() expect(component.isEdit).toBe('true') // 断言弹窗方法被触发 expect(modalDialog.openModalDialog).toHaveBeenCalled() })
3. 优化第二个测试用例
移除对showMessagesList的Mock,真实测试分支逻辑:
it('should trigger dialog when isEdit is true', () => { const modalDialog = TestBed.inject(ModalDialogComponent); localStorage.setItem('isEditMode', 'true') component.showMessagesList() expect(modalDialog.openModalDialog).toHaveBeenCalled() })
4. 可选:隔离localStorage影响
为避免测试间的localStorage数据污染,在每个测试前清理:
beforeEach(() => { localStorage.clear(); component.content = mockContent })
修改后的完整spec.ts代码
import { ComponentFixture, TestBed } from '@angular/core/testing' import { By } from '@angular/platform-browser' import { ModalDialogComponent, ModalDialogModule, } from 'modal-dialog' import { ContentModel } from '../../model/content.model' import * as componentTypes from '../componentTypes' import { ComposeComponent } from '../compose-message/compose.component' import { MessageItemsComponent } from '../message-list/message-item.component' import { SideBarDrawerComponent } from './side-bar-drawer.component' window.ResizeObserver = window.ResizeObserver || jest.fn().mockImplementation(() => ({ disconnect: jest.fn(), observe: jest.fn(), unobserve: jest.fn(), })) describe('SideBarDrawerComponent ', () => { let component: SideBarDrawerComponent let fixture: ComponentFixture<SideBarDrawerComponent> beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ModalDialogModule], declarations: [ SideBarDrawerComponent, MessageItemsComponent , ComposeComponent, ModalDialogComponent, ], providers: [ { provide: Window, useValue: window }, { provide: ModalDialogComponent, useValue: { openModalDialog: jest.fn() } }, ], }) .compileComponents() .then(() => { fixture = TestBed.createComponent(SideBarDrawerComponent) component = fixture.componentInstance }) }) beforeEach(() => { localStorage.clear(); component.content = mockContent }) it('should call openModalDialog when isEditMode is true', () => { localStorage.setItem('isEditMode', 'true') const modalDialog = TestBed.inject(ModalDialogComponent); component.showMessagesList() expect(component.isEdit).toBe('true') expect(modalDialog.openModalDialog).toHaveBeenCalled() }) it('should trigger dialog when isEdit is true', () => { const modalDialog = TestBed.inject(ModalDialogComponent); localStorage.setItem('isEditMode', 'true') component.showMessagesList() expect(modalDialog.openModalDialog).toHaveBeenCalled() }) })
内容的提问来源于stack exchange,提问作者Vijay
相关产品推荐
相关产品推荐

