You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular Material Dialog测试报错:Cannot read properties of undefined (reading 'push')

解决Angular测试中MatDialog.open的TypeError问题

错误信息

TypeError: Cannot read properties of undefined (reading 'push')
at MatDialog.open (node_modules/@angular/material/fesm2022/dialog.mjs:598:26)
at MatDialog.open (node_modules/@angular/material/fesm2022/dialog.mjs:598:26)
at UserComponent.deleteUser (src/app/components/user/user.component.ts:108:35)
at UserContext.apply (src/app/components/user/user.component.spec.ts:147:15)
at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:369:28)
at ProxyZoneSpec.onInvoke (node_modules/zone.js/fesm2015/zone-testing.js:2081:39)
at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:368:34)
at ZoneImpl.run (node_modules/zone.js/fesm2015/zone.js:111:43)
at runInTestZone (node_modules/zone.js/fesm2015/zone-testing.js:216:38)
at UserContext. (node_modules/zone.js/fesm2015/zone-testing.js:234:32)
at
at UserContext.apply (src/app/components/user/user.component.spec.ts:147:15)
at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:369:28)
at ProxyZoneSpec.onInvoke (node_modules/zone.js/fesm2015/zone-testing.js:2081:39)
at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:368:34)
at ZoneImpl.run (node_modules/zone.js/fesm2015/zone.js:111:43)
at runInTestZone (node_modules/zone.js/fesm2015/zone-testing.js:216:38)

UserComponent相关代码

deleteUser(userId: number, userNickname: string): void {
  const dialogRef = this.dialog.open(DeleteDialogComponent, {
    height: '400px',
    width: '550px',
    data: { userId, userNickname },
  });

  dialogRef.afterClosed().subscribe(result => {
    if (result) {
      const subscription = this.userService.deleteUser(userId).subscribe({
        next: () => {
          console.log("Ok!")
        },
        error: error => {
        }
      });
    }
  });
}

原测试代码

describe('UserComponent', () => {
  let component: UserComponent;
  let fixture: ComponentFixture<UserComponent>;
  let userServiceMock: jasmine.SpyObj<UserService>;
  let routerMock: jasmine.SpyObj<Router>;
  let dialogSpy: jasmine.SpyObj<MatDialog>;

  beforeEach(async () => {
    userServiceMock = jasmine.createSpyObj<UserService>('UserService', {
      deleteUser: of(1)
    });
    routerMock = jasmine.createSpyObj<Router>('Router', ['navigate']);
    dialogSpy = jasmine.createSpyObj('MatDialog', ['open']);

    await TestBed.configureTestingModule({
      imports: [UserComponent, MatDialogModule, BrowserAnimationsModule],
      providers: [
        { provide: UserService, useValue: userServiceMock },
        { provide: Router, useValue: routerMock },
        { provide: MatDialog, useValue: dialogSpy }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(UserComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should open modal and delete user', () => {
    const userId = 1;
    const userNickname = 'JohnSmith';

    component.deleteUser(userId, userNickname);

    const dialogRef = dialogSpy.open.calls.mostRecent().returnValue;
    dialogRef.afterClosed = jasmine.createSpy().and.returnValue(of(true));

    component.deleteUser(userId, userNickname);
    
    expect(userServiceMock.deleteUser).toHaveBeenCalledWith(userId);
  });
});

问题分析与解决方案

核心问题

  1. dialogSpy仅Mock了open方法,但MatDialog内部维护的openDialogs数组未初始化,导致调用open时执行this.openDialogs.push时报错。
  2. 测试逻辑错误:第一次调用deleteUser时,open方法返回undefined,后续修改返回值的操作无效,且重复调用deleteUser不符合测试预期。

修正后的测试代码

describe('UserComponent', () => {
  let component: UserComponent;
  let fixture: ComponentFixture<UserComponent>;
  let userServiceMock: jasmine.SpyObj<UserService>;
  let routerMock: jasmine.SpyObj<Router>;
  let dialogSpy: jasmine.SpyObj<MatDialog>;
  // 提前创建dialogRef的Spy对象
  let dialogRefSpy: jasmine.SpyObj<any>;

  beforeEach(async () => {
    userServiceMock = jasmine.createSpyObj<UserService>('UserService', {
      deleteUser: of(1)
    });
    routerMock = jasmine.createSpyObj<Router>('Router', ['navigate']);
    
    // 初始化dialogRef的Spy,预设afterClosed方法的返回值
    dialogRefSpy = jasmine.createSpyObj('MatDialogRef', ['afterClosed']);
    dialogRefSpy.afterClosed.and.returnValue(of(true));

    // 创建MatDialog的Spy,同时初始化openDialogs数组,并让open返回预设的dialogRefSpy
    dialogSpy = jasmine.createSpyObj('MatDialog', ['open'], {
      openDialogs: []
    });
    dialogSpy.open.and.returnValue(dialogRefSpy);

    await TestBed.configureTestingModule({
      imports: [UserComponent, MatDialogModule, BrowserAnimationsModule],
      providers: [
        { provide: UserService, useValue: userServiceMock },
        { provide: Router, useValue: routerMock },
        { provide: MatDialog, useValue: dialogSpy }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(UserComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should open modal and delete user when dialog confirms', () => {
    const userId = 1;
    const userNickname = 'JohnSmith';

    // 调用目标方法
    component.deleteUser(userId, userNickname);

    // 验证对话框是否按预期打开
    expect(dialogSpy.open).toHaveBeenCalledWith(DeleteDialogComponent, {
      height: '400px',
      width: '550px',
      data: { userId, userNickname },
    });

    // 验证用户服务的deleteUser方法是否被触发
    expect(userServiceMock.deleteUser).toHaveBeenCalledWith(userId);
  });
});

关键修改说明

  • 初始化openDialogs数组:在创建dialogSpy时,通过第三个参数给MatDialog实例添加openDialogs: [],解决内部push操作的undefined问题。
  • 预设dialogRef的返回值:提前创建包含afterClosed方法的dialogRefSpy,并让dialogSpy.open直接返回该对象,确保调用deleteUser时能正确获取到有效的dialogRef。
  • 调整测试逻辑:去掉重复调用deleteUser的代码,先设置好所有Spy的返回值,再执行目标方法,最后依次验证对话框打开和用户删除的行为。

内容的提问来源于stack exchange,提问作者Polkov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 14:53:11