Angular测试:如何监视函数内dialogRef.afterClosed()方法?
问题
我有一个Angular组件方法,打开CustomDialog对话框,对话框关闭后会删除传入的元素。想要监视该方法中的dialogRef.afterClosed().subscribe逻辑,但测试时抛出错误:TypeError: dialogRef.afterClosed is not a function。原本以为配置MatDialog的Mock对象{ provide: MatDialog, useValue: { afterClosed: () => of(true), open: () => of(true)}}能生效,但实际没起作用。请问如何正确监视dialogRef.afterClosed()?
组件代码
// 注意:原代码写法有误,需通过构造函数注入MatDialog constructor(private deleteDialog: MatDialog) {} onDeleteElementClicked($event: any, elementName: string): void { const dialogRef = this.deleteDialog.open(CustomDialog, { data: { // 自定义数据 } }); dialogRef.afterClosed().subscribe(result => { this.toggleFlagAndCall(result, () => { this.deleteElement(elementName); }); }); }
原测试代码(存在问题)
describe('DeleteComponent', () => { let component: DeleteComponent; let fixture: ComponentFixture<DeleteComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [DeleteComponent], imports: [/* 依赖导入 */], providers: [ { provide: MatDialogRef, useClass: MatDialogRef, useValue: { afterOpen: () => of(true)}}, { provide: DeleteDialogComponent, useValue: { afterClosed: () => of(true)}}, { provide: MatDialog, useValue: { afterClosed: () => of(true), open: () => of(true)}}, ] }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(DeleteComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should delete a single element', () => { const event = { preventDefault: () => {}, stopPropagation: () => {} }; component.onDeleteElementClicked(event, 'toBeDeletedElement'); expect(component.elements.length).toBe(0); }); });
问题原因
你的Mock配置逻辑错误:
- MatDialog的
open方法应该返回一个模拟的DialogRef对象,而不是直接返回of(true)。组件中是调用dialogRef.afterClosed(),这个dialogRef就是open方法的返回值。 - 多余的
MatDialogRef和DeleteDialogComponent配置完全没必要,反而会干扰测试。
正确解决方案
步骤1:正确Mock MatDialog
配置MatDialog的useValue时,让open方法返回一个包含afterClosed方法的对象,该方法返回你需要的Observable结果(比如of(true)表示用户确认删除)。
步骤2:移除无效Providers
删掉MatDialogRef和DeleteDialogComponent的provide配置,测试中不需要直接提供这些依赖。
步骤3:完善测试逻辑
可以选择监视deleteElement方法,验证它是否在对话框确认后被调用;或者直接断言元素列表的变化。
修改后的完整测试代码
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatDialog } from '@angular/material/dialog'; import { of } from 'rxjs'; import { DeleteComponent } from './delete.component'; import { CustomDialog } from './custom-dialog.component'; describe('DeleteComponent', () => { let component: DeleteComponent; let fixture: ComponentFixture<DeleteComponent>; let mockMatDialog: jasmine.SpyObj<MatDialog>; let deleteElementSpy: jasmine.Spy; beforeEach(async () => { // 创建MatDialog的spy对象,包含open方法 mockMatDialog = jasmine.createSpyObj('MatDialog', ['open']); // 配置open方法返回模拟的dialogRef,包含afterClosed方法 mockMatDialog.open.and.returnValue({ afterClosed: () => of(true) // 返回用户确认删除的结果 }); await TestBed.configureTestingModule({ declarations: [DeleteComponent], imports: [/* 你的依赖导入,比如MatDialog测试模块 */], providers: [ { provide: MatDialog, useValue: mockMatDialog } ] }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(DeleteComponent); component = fixture.componentInstance; // 初始化测试用元素列表 component.elements = [{ name: 'toBeDeletedElement' }]; // 监视deleteElement方法,验证调用情况 deleteElementSpy = spyOn(component, 'deleteElement').and.callThrough(); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should delete element after dialog confirmation', () => { const event = { preventDefault: () => {}, stopPropagation: () => {} }; component.onDeleteElementClicked(event, 'toBeDeletedElement'); // 验证对话框是否正确打开 expect(mockMatDialog.open).toHaveBeenCalledWith(CustomDialog, jasmine.any(Object)); // 验证删除方法是否被调用 expect(deleteElementSpy).toHaveBeenCalledWith('toBeDeletedElement'); // 验证元素列表长度变化 expect(component.elements.length).toBe(0); }); });
额外说明
- 组件中的
deleteDialog必须通过构造函数注入(原代码deleteDialog = MatDialog是错误写法,会导致运行时无法获取实际的MatDialog实例)。 - 如果需要测试用户取消对话框的场景,只需把
afterClosed: () => of(false),此时应断言deleteElement没有被调用。
内容的提问来源于stack exchange,提问作者JBoy
相关产品推荐
相关产品推荐

