使用“材质对话框”发出警报的组件上的角单元测试未初始化

时间:2019-03-10 01:51:03

标签: angular jasmine angular-material

浏览了StackOverflow和其他论坛上的许多主题之后,我放弃尝试并愿意将我的问题发布为问题。

我有一个组件,该组件使用“材质对话框”显示我的应用程序的警报,例如“确认”弹出窗口或“信息”弹出窗口。我创建了一个名为AlertsComponent的组件,并在我想显示警报的任何地方的父组件中使用它。我有自己的模型来处理信息。所有这些都工作正常,但是即使在create / initialize事件上,spec.ts(测试用例)也失败了。

我的AlertsComponent.ts:

import { Component, OnInit, Optional, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { AlertInfo } from 'src/Model/common/alert-info.model';

@Component({
  selector: 'app-alerts',
  templateUrl: './alerts.component.html',
  styleUrls: ['./alerts.component.css']
})
export class AlertsComponent implements OnInit {

  constructor(
    private dialogRef: MatDialogRef<AlertsComponent>,
    @Optional() @Inject(MAT_DIALOG_DATA) public alertInfo?: AlertInfo
  ) {
    console.log('Alert Data: ' + JSON.stringify(alertInfo));
    if (alertInfo.ConfirmPopup) {
      alertInfo.Header = 'Confirm ?';
    } else { alertInfo.Header = 'Alert'; }
    this.dialogRef.disableClose = true;
  }

  ngOnInit() {
  }

  ConfirmResponse(response: boolean): void {
    this.dialogRef.close(response);
  }

  CloseAlert() {
    this.dialogRef.close();
  }

}

我的HTML看起来像:

<div>
  <h2 mat-dialog-title>{{alertInfo.Header}}</h2>
  <hr/>
  <mat-dialog-content>
      <strong>{{alertInfo.Body}}</strong>
      <br>
      <br>
      <!-- <strong>{{data}}</strong> -->
    </mat-dialog-content>
    <hr>
    <mat-dialog-actions>
      <div>
        <ng-container *ngIf="alertInfo.ConfirmPopup; else alertOnly">
            <button mat-button class="align-self-center" color="primary" class="button-space" (click)="ConfirmResponse(true);">YES</button>
            <button mat-button class="align-self-center" color="primary" class="button-space" (click)="ConfirmResponse(false);">NO</button>
        </ng-container>
        <ng-template #alertOnly>
            <button mat-button color="primary" class="button-space" (click)="CloseAlert();">OK</button>
        </ng-template>
      </div>
    </mat-dialog-actions>
</div>

我的说明是:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { AlertsComponent } from './alerts.component';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material';
import { AlertInfo } from 'src/Model/common/alert-info.model';
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';

describe('AlertsComponent', () => {
  let component: AlertsComponent;
  let fixture: ComponentFixture<AlertsComponent>;
  let mockDialogRef: MatDialogRef<AlertsComponent>;
  let mockAlertInfoObj: AlertInfo;
  // const MY_MAT_MOCK_TOKEN = new InjectionToken<AlertInfo>('Mock Injection Token', {
  //   providedIn: 'root',
  //   factory: () => new AlertInfo()
  // });

  @Component({
    selector: 'app-alerts',
    template: '<div><mat-dialog-content></mat-dialog-content></div>'
  })

  class MockAlertsComponent { }

  mockDialogRef = TestBed.get(MatDialogRef);
  mockAlertInfoObj = new AlertInfo();
  mockAlertInfoObj.ConfirmPopup = false;
  mockAlertInfoObj.Body = 'test alert';

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ AlertsComponent, MockAlertsComponent ],
      imports: [MatDialogModule],
      providers: [
        {provide: MatDialogRef, useValue: mockDialogRef},
        {provide: MAT_DIALOG_DATA, useValue: mockAlertInfoObj},
      ],
      schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
    })
    .compileComponents();
  }));

  TestBed.overrideModule(BrowserDynamicTestingModule, {
    set: {
      entryComponents: [AlertsComponent]
    }
  })

  beforeEach(() => {
    fixture = TestBed.createComponent(AlertsComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

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

当我运行“ ng test”时,此组件测试用例失败,并显示错误消息:

AlertsComponent encountered a declaration exception
Error: Cannot call Promise.then from within a sync test.
Error: Cannot call Promise.then from within a sync test.
    at SyncTestZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.SyncTestZoneSpec.onScheduleTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:366:1)
    at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.scheduleTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:404:1)
    at Zone../node_modules/zone.js/dist/zone.js.Zone.scheduleTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:238:1)
    at Zone../node_modules/zone.js/dist/zone.js.Zone.scheduleMicroTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:258:1)
    at scheduleResolveOrReject (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:879:1)
    at ZoneAwarePromise.then (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:1012:1)
    at ApplicationInitStatus.push../node_modules/@angular/core/fesm5/core.js.ApplicationInitStatus.runInitializers (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm5/core.js:15618:1)
    at TestBedViewEngine.push../node_modules/@angular/core/fesm5/testing.js.TestBedViewEngine._initIfNeeded (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm5/testing.js:1702:59)
    at TestBedViewEngine.push../node_modules/@angular/core/fesm5/testing.js.TestBedViewEngine.get (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm5/testing.js:1766:1)
    at Function.push../node_modules/@angular/core/fesm5/testing.js.TestBedViewEngine.get (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm5/testing.js:1551:1)

我不知道或无法弄清楚,我在哪里做错什么,或者在哪里?有人可以帮我吗?

2 个答案:

答案 0 :(得分:0)

@erbsenkoenig的堆叠闪电有所帮助,然后我又添加了一些东西来解决我的需求。下面是我做的模仿和MatDialog。

    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.
  public open(inputdata: any) {
    return {
      afterClosed: () => of({inputdata})
    };
  }
}

老实说,我从其他Stackoverflow答案中引用了这一点。在提供程序中使用了此模拟类。然后将我的测试实例化为

// arrange
const mockAddEditDialogObj = MatDialogMock.prototype;
let dialogRef = jasmine.createSpyObj(mockAddEditDialogObj.open.name, ['afterClosed']);
dialogRef.afterClosed.and.returnValue(of(true));

// act
component.AddNew();
dialogRef = mockAddEditDialogObj.open(EditProjectComponent.prototype);
const result = dialogRef.afterClosed();

// assert
expect(dialogRef).toBeTruthy();

您可以根据需要扩展测试和模拟类返回对象。

答案 1 :(得分:0)

因此,首先,我将使用浅层测试方法来测试该组件,并使用如下测试设置:

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

  let test: AlertInfo = {Header: 'HEADER', Body: 'BODY'};

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [AppComponent, TestMatDialogActionsComponent, TestMatDialogContentComponent],
      providers: [
        {provide: MatDialogRef, useValue: {}},
        {provide: MAT_DIALOG_DATA, useValue: test}
      ],
      schemas: [NO_ERRORS_SCHEMA]
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
  });

  it('should create', () => {
    fixture.detectChanges();
    expect(component).toBeTruthy();
  });
});

在此设置中,需要TestMatDialogActionsComponentTestMatDialogContentComponent来模拟材质对话框的内容。

这些测试组件可以在spec文件本身中声明(但不导出),也可以在src文件夹旁边创建一个中央测试文件夹,您可以在其中放置这些组件并进行导出,以便重复使用在您的测试中。但是请确保仅将此文件夹包含在tsconfig.spec.ts内,而不要包含在tsconfig.app.ts内,以确保在应用程序内不会意外使用此组件。

@Component({
  selector: '[mat-dialog-actions]',
  template: '<ng-content></ng-content>'
})
export class TestMatDialogActionsComponent {

  constructor() { }
}

@Component({
  selector: '[mat-dialog-content]',
  template: '<ng-content></ng-content>'
})
export class TestMatDialogContentComponent {

  constructor() { }
}

从该设置开始,您可以添加所需的所有内容,以测试用例。