Angular单元测试报错:Cannot set property 'componentInstance' of undefined
尝试了多种方案(构造函数注入组件、以不同方式创建mock和spy、将spy注入元素等),但始终遇到相同错误:Cannot set property 'componentInstance' of undefined,恳请提供解决思路。
组件代码
openUpdate(rowSelected: iData) { let dialogRef = this.dialog.open(DialogEditComponent, {}); dialogRef.componentInstance.dialogRef = dialogRef; dialogRef.componentInstance.selectedData = rowSelected; const sub = dialogRef.componentInstance.onAdd.subscribe((data: iData) => { if (data) { this.update(data); } }); dialogRef.afterClosed().subscribe(() => { sub.unsubscribe(); }); }
测试代码(SPEC.TS)
describe('Component1Component', () => { let component: Componenet1Component; let fixture: ComponentFixture<Componenet1Component>; let dataServiceSpy: jasmine.SpyObj<DataService>; let toastrServiceSpy: jasmine.SpyObj<ToastrService>; let dialogSpy: any; let dialogRefSpy: any; beforeEach(async () => { dialogSpy = { open: jasmine.createSpy('open').and.returnValue({ componentInstance: { onAdd: jasmine.createSpyObj('onAdd', ['subscribe']) }, afterClosed: () => { return jasmine.createSpyObj('afterClosed', ['subscribe']); }, }) }; toastrServiceSpy = jasmine.createSpyObj('ToastrService', ['success', 'error']); await TestBed.configureTestingModule({ declarations: [Component1Component, DialogEditComponent], imports: [MatDialogModule], providers: [ { provide: MatDialog, useValue: dialogSpy }, { provide: MatDialogRef, useValue: dialogRefSpy }, { provide: FinalistService, useValue: dataServiceSpy }, { provide: ToastrService, useValue: toastrServiceSpy }, { provide: MAT_DIALOG_DATA, useValue: {} }, ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(CampaignFinalistsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should open the edit dialog with the selected data', async () => { component = new Component1Component(dialogSpy, dataServiceSpy, toastrServiceSpy); dialogRefSpy.componentInstance = { dialogRef: dialogRefSpy, selectedFinalist: mockData[0], onAdd: of(mockData[0]) }; dialogSpy.open.and.returnValue(dialogRefSpy); spyOn(component, 'update'); component.openUpdate(mockData[0]); expect(dialogSpy.open).toHaveBeenCalledWith(DialogEditComponent, {}); expect(dialogRefSpy.componentInstance.selectedData).toEqual(mockData[0]); expect(component.update).toHaveBeenCalledWith(mockData[0]); expect(dialogRefSpy.afterClosed).toHaveBeenCalled(); });
报错信息
TypeError: Cannot set property 'componentInstance' of undefined at UserContext.<anonymous> (src/app/features/Component1.component.spec.ts:108:35) at Generator.next (<anonymous>) at asyncGeneratorStep (node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js:3:1) at _next (node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js:22:1) at executor (node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js:27:1) at new ZoneAwarePromise (node_modules/zone.js/dist/zone.js:1351:25) at UserContext.apply (node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js:19:1) at _ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:409:30) at ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/zone-testing.js:303:43) at _ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:408:56)
先初始化
dialogRefSpy
报错核心原因是dialogRefSpy未赋值就直接设置componentInstance属性。在测试用例或beforeEach里先给它初始化一个基础结构:dialogRefSpy = { componentInstance: {}, afterClosed: jasmine.createSpy('afterClosed').and.returnValue(of(null)) };统一mock结构,避免重复定义
原本beforeEach里已经给dialogSpy.open返回了一个mock的dialogRef,测试用例里又重新替换成dialogRefSpy,导致结构混乱。建议在beforeEach里统一初始化dialogRefSpy,并让dialogSpy.open直接返回它:beforeEach(async () => { // 先初始化完整的dialogRefSpy dialogRefSpy = { componentInstance: { onAdd: new Subject(), // 用Subject方便后续触发订阅回调 dialogRef: null, selectedData: null }, afterClosed: jasmine.createSpy('afterClosed').and.returnValue(of(null)) }; dialogRefSpy.componentInstance.dialogRef = dialogRefSpy; dialogSpy = { open: jasmine.createSpy('open').and.returnValue(dialogRefSpy) }; // ...其他初始化代码 });修复组件实例创建方式
测试用例里直接new Component1Component会绕开TestBed的依赖注入,建议直接用fixture.componentInstance;另外dataServiceSpy也没初始化,要提前创建:// 在beforeEach里初始化dataServiceSpy dataServiceSpy = jasmine.createSpyObj('DataService', ['update']);正确触发
onAdd事件验证逻辑
如果用Subject模拟onAdd,调用openUpdate后手动触发next,才能验证update是否被正确调用:component.openUpdate(mockData[0]); dialogRefSpy.componentInstance.onAdd.next(mockData[0]); // 触发订阅回调 expect(component.update).toHaveBeenCalledWith(mockData[0]);移除不必要的依赖
测试Component1Component时,不需要提供MatDialogRef和MAT_DIALOG_DATA,这两个是给弹窗组件DialogEditComponent用的,删掉这两个provider避免干扰。
内容的提问来源于stack exchange,提问作者Kenzo_Gilead

