【问题标题】:MatDialog Service Unit Test Angular 6 ErrorMatDialog 服务单元测试 Angular 6 错误
【发布时间】: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。嘿,谢谢您的建议,但我正在寻找单元测试答案。该服务运行良好,在组件中被调用,但在单元测试中没有

标签: javascript angular unit-testing jasmine karma-jasmine


【解决方案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 })

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-12-17
        • 2019-05-06
        • 2019-03-24
        • 2017-06-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多