MatDialog 服务单元测试 Angular 6 错误

Posted

技术标签:

【中文标题】MatDialog 服务单元测试 Angular 6 错误【英文标题】:MatDialog Service Unit Test Angular 6 Error 【发布时间】:2019-03-30 07:41:21 【问题描述】:

我有模式服务来打开、确认和关闭对话框,我正在制作它的单元测试文件,但我在 Angular 上遇到错误,这是代码。

modal.service.ts

@Injectable()
export class ModalService 

  constructor(private dialog: MatDialog)  

  public open<modalType>(modalComponent: ComponentType<modalType>): Observable<any> 
    let dialogRef: MatDialogRef<any>;

    dialogRef = this.dialog.open(modalComponent, 
      maxWidth: '100vw'
    );
    console.log(dialogRef)
    dialogRef.componentInstance.body = body;

    return dialogRef.afterClosed().pipe(map(result => console.log('test'); );
  


modal.service.spec.ts

export class TestComponent  


describe('ModalService', () => 
  let modalService: ModalService;

  const mockDialogRef = 
    open: jasmine.createSpy('open')
  ;

  beforeEach(async(() => 
    TestBed.configureTestingModule(
      imports: [ MatDialogModule ],
      providers: [
        ModalService,
        MatDialogRef,
         provide: MatDialog, useClass: MatDialogStub 
      ]
    ).compileComponents();

    modalService = TestBed.get(ModalService);
  ));


  it('open modal', () => 
    modalService.open(DummyComponent, '300px');
    expect(modalService.open).toHaveBeenCalled();

  );

);

所以使用该代码的错误是

TypeError: Cannot read property 'componentInstance' of undefined

你能帮助我如何使这个成功吗?非常感谢您的帮助。

【问题讨论】:

检查这个mat对话框的例子,确保所有需要的模块都已经导入stackblitz.com/angular/gxyboyyobmo @DanielC。嘿,谢谢您的建议,但我正在寻找单元测试答案。该服务运行良好,在组件中被调用,但在单元测试中没有 【参考方案1】:

测试mat-dialogs 可能很棘手。我倾向于使用间谍对象从打开的对话框(下面的dialogRefSpyObj)返回,这样我可以更轻松地跟踪和控制测试。在您的情况下,它可能类似于以下内容:

describe('ModalService', () => 
    let modalService: ModalService;
    let dialogSpy: jasmine.Spy;
    let dialogRefSpyObj = jasmine.createSpyObj( afterClosed : of(), close: null );
    dialogRefSpyObj.componentInstance =  body: '' ; // attach componentInstance to the spy object...

    beforeEach(() => 
        TestBed.configureTestingModule(
            imports: [MatDialogModule],
            providers: [ModalService]
        );
        modalService = TestBed.get(ModalService);
    );

    beforeEach(() => 
        dialogSpy = spyOn(TestBed.get(MatDialog), 'open').and.returnValue(dialogRefSpyObj);
    );

    it('open modal ', () => 
        modalService.open(TestComponent, '300px');
        expect(dialogSpy).toHaveBeenCalled();

        // You can also do things with this like:
        expect(dialogSpy).toHaveBeenCalledWith(TestComponent,  maxWidth: '100vw' );

        // and ...
        expect(dialogRefSpyObj.afterClosed).toHaveBeenCalled();
    );
);

【讨论】:

@dmcgrandie 我同意 matdialog 测试很棘手,感谢您的解决方案。它对我有用。谢谢你 如何触发dialogRefSpyObj.afterClosed @PinguinoSod - 如何触发它取决于您的代码。请随时就您的详细信息提出另一个问题。请务必包含代码示例,展示您迄今为止所做的尝试。 您将如何测试由另一个模态打开的模态? (应用程序>单击按钮>第一个基本确认模式>单击是>在新模式中打开组件) 我会独立测试这两个模态(每个都有自己的一组测试)。【参考方案2】:

我有一个更好的解决方案,在 2019 年仍然有效

header.component.ts

import  BeforeLogOutComponent  from '@app-global/components/before-log-out/before-log-out.component';


  /**
   * The method triggers before the logout.
   * Opens the dialog and warns the user before log Out.
   */
  public beforeLogOut(): void 
    this._dialog.open(BeforeLogOutComponent,  width: '400px', disableClose: false, panelClass: 'dialog_before_log_out' )
    .afterClosed()
    .subscribe((res) => 
      if (res && res.action === true)  this.loggedOut(); 
    , err => 
      console.error(err);
    );
  

header.component.spec.ts

import  async, ComponentFixture, TestBed  from '@angular/core/testing';
import  MatDialog  from '@angular/material';
import  Observable, of  from 'rxjs';



<<-- Create a MatDialog mock class -->>
export class MatDialogMock 
  // When the component calls this.dialog.open(...) we'll return an object
  // with an afterClosed method that allows to subscribe to the dialog result observable.
  open() 
    return 
      afterClosed: () => of(action: true)
    ;
  




describe('HeaderComponent', () => 

  let component: HeaderComponent;
  let fixture: ComponentFixture<HeaderComponent>;

  beforeEach(async(() => 

    TestBed.configureTestingModule(
      imports: [
        MaterialModule, RouterTestingModule, HttpModule, BrowserAnimationsModule,
        HttpClientModule, FlexLayoutModule,
      ],
      declarations: [
        HeaderComponent,
      ],
      providers: [
         provide: MatDialog, useClass: MatDialogMock  <<-- look this
      ]
    )
    .compileComponents();
  ));



  beforeEach(async() => 
    fixture = TestBed.createComponent(HeaderComponent);
    component = fixture.componentInstance;


    component.ngOnInit();
    component.ngAfterViewInit();
    await fixture.whenStable();
    fixture.detectChanges();
  );


  fit('should create', () => 
    expect(component).toBeTruthy();
  );


  // I test the dialog here.
  fit('should open the dialog', () => 
    component.beforeLogOut();
  );



【讨论】:

实际上,我上面其他答案中的间谍对象解决方案在 2019 年的 Angular 7 中工作得很好。:) 我更喜欢你提供的模拟类方法,尽管它们都可以解决问题,但它是只是选择哪一个的偏好问题。 hmm,不确定这将如何测试 afterClosed,因为它实际上从未被调用过,因为在规范文件中没有告诉对话框关闭。 请考虑解释您的代码如何帮助解决 OP 的问题,而不是简单地灌输代码。 谢谢,它在我的情况下工作,模拟整个对话响应,解决了问题并根据需要返回真/假。【参考方案3】:

对于您的情况,我没有确切的答案,但我也对MatDialog 进行了一些测试。我可以告诉你我做了什么。也许看看inject() 部分:

(为了清晰和保密,我删除了一些内容)

describe('MyDialogComponent', () => 
  let dialog: MatDialog;
  let overlayContainer: OverlayContainer;
  let component: MyDialogComponent;
  let fixture: ComponentFixture<MyDialogComponent>;
  const mockDialogRef = 
    close: jasmine.createSpy('close')
  ;

  beforeEach(async(() => 
    TestBed.configureTestingModule(
      imports: [
        BrowserAnimationsModule,
        ReactiveFormsModule,
        AngularMaterialModule,
      ],
      providers: [
         provide: MatDialogRef, useValue: mockDialogRef ,
        
          provide: MAT_DIALOG_DATA,
          useValue: 
            title: 'myTitle',
          
        
      ],
      declarations: [MyDialogComponent],
    );

    TestBed.overrideModule(BrowserDynamicTestingModule, 
      set: 
        entryComponents: [MyDialogComponent]
      
    );

    TestBed.compileComponents();
  ));

  beforeEach(inject([MatDialog, OverlayContainer],
    (d: MatDialog, oc: OverlayContainer) => 
      dialog = d;
      overlayContainer = oc;
    )
  );

  afterEach(() => 
    overlayContainer.ngOnDestroy();
  );

  beforeEach(() => 
    fixture = TestBed.createComponent(MyDialogComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  );

  it('should create', () => 
    expect(component).toBeTruthy();
  );


  it('onCancel should close the dialog', () => 
    component.onCancel();
    expect(mockDialogRef.close).toHaveBeenCalled();
  );

);

【讨论】:

【参考方案4】:

将此部分添加到提供程序部分

 provide: MAT_DIALOG_DATA, useValue:  ,
 provide: MatDialogRef, useValue:  ,

查看下方

describe('MyDialogComponent', () => 
   let component: MyDialogComponent;
   let fixture: ComponentFixture<MyDialogComponent>;

  beforeEach(async(() => 
    TestBed.configureTestingModule(
       imports: [
          MatDialogModule,
       ],
       declarations: [MyDialogComponent],
       providers: [
           provide: MAT_DIALOG_DATA, useValue:  ,
           provide: MatDialogRef, useValue:  ,
       ],
    ).compileComponents();
   ));
 );

【讨论】:

【参考方案5】:

此答案不直接回答问题,但适用于像我这样的人,因为您在打开对话框后无法查询对话框。

使用spectator,您只需将 root: true 作为查询的选项。

找不到对话框,因为它不是您正在测试的组件的子组件,但使用 root: true 将搜索整个页面。

例如:spectator.query(byTestId('myTestId'), root: true )

【讨论】:

以上是关于MatDialog 服务单元测试 Angular 6 错误的主要内容,如果未能解决你的问题,请参考以下文章

Angular - 模块 AppModule 导入的意外值 MatDialog

在我的函数 ngDoCheck() Angular 中关闭 matDialog 时出现问题

Angular - 模块AppModule导入的意外值MatDialog

Angular Material MatDialog 未正确销毁

如何使用 MatDialog 将行数据或数组对象传递给 Angular 中的组件

防止 MatDialog 被拖出窗口