Angular Jest测试打开MatDialog的组件-open不是函数

时间:2020-06-24 15:31:07

标签: angular unit-testing angular-material mocking jestjs

类似于this问题,但没有提供对我有用的答案。

我有一个简单的组件,该组件具有打开对话框的方法:

  enterGiveaway() {
    this.dialog.open(SpendTicketsDialogComponent, {
      width: '370px',
      height: '600px'
    });
  }

现在我只想测试调用该方法是否会导致对话框打开。

测试失败,并显示以下错误:

  expect(spy).toBeCalledTimes(expected)

    Expected number of calls: 1
    Received number of calls: 0

使用以下代码:

    import {async, ComponentFixture, TestBed} from '@angular/core/testing';
    
    import {GiveawayItemComponent} from './giveaway-item.component';
    import {giveawaysMock} from '../../../../../mocks/giveaways.mock';
    import {MaterialModule} from '../../material.module';
    import {getTranslocoModule} from '../../../transloco-testing.module';
    import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
    import {MatDialog} from '@angular/material/dialog';
    import {EMPTY} from 'rxjs';
    import {SpendTicketsDialogComponent} from '../dialogs/tickets-dialog/spend-tickets-dialog.component';
    import {NumberFormatter} from '../../filters/numberFormatter/numberFormatter.filter';
    import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
    import {BrowserModule} from '@angular/platform-browser';
    
    describe('GiveawayItemComponent', () => {
      let component: GiveawayItemComponent;
      let fixture: ComponentFixture<GiveawayItemComponent>;
      let dialog: any;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [
            GiveawayItemComponent,
            SpendTicketsDialogComponent,
            NumberFormatter
          ],
          imports: [
            MaterialModule,
            BrowserAnimationsModule,
            getTranslocoModule({})
          ],
          schemas: [CUSTOM_ELEMENTS_SCHEMA]
        })
          .overrideModule(BrowserModule, {
            set: {entryComponents: [SpendTicketsDialogComponent]}
          })
          .compileComponents();
      }));
    
      beforeEach(() => {
        fixture = TestBed.createComponent(GiveawayItemComponent);
        component = fixture.componentInstance;
        component.giveaway = giveawaysMock[0];
        component.numberOfChances = 100;
        dialog = TestBed.inject(MatDialog);
        fixture.detectChanges();
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    
      describe('enterGiveaway', () => {
        it('should open the spend tickets dialog', async(() => {
          component.enterGiveaway();
          fixture.detectChanges();
          const spy = spyOn(dialog, 'open').and.returnValue({
            afterClosed: () => EMPTY
          });
    
          expect(spy).toBeCalledTimes(1);
        }));
      });
    });

我当然知道,MatDialog没有引用实际的SpendTicketsDialogComponent,后者是打开的那个。因此,我尝试为对话框提供一个模拟对象:

    import {async, ComponentFixture, TestBed} from '@angular/core/testing';
    
    import {GiveawayItemComponent} from './giveaway-item.component';
    import {giveawaysMock} from '../../../../../mocks/giveaways.mock';
    import {MaterialModule} from '../../material.module';
    import {getTranslocoModule} from '../../../transloco-testing.module';
    import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
    import {MatDialog} from '@angular/material/dialog';
    import {of} from 'rxjs';
    import {SpendTicketsDialogComponent} from '../dialogs/tickets-dialog/spend-tickets-dialog.component';
    import {NumberFormatter} from '../../filters/numberFormatter/numberFormatter.filter';
    import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
    import {BrowserModule} from '@angular/platform-browser';
    
    class dialogMock {
      open() {
        return {
          afterClosed: () => of({})
        };
      }
    }
    
    describe('GiveawayItemComponent', () => {
      let component: GiveawayItemComponent;
      let fixture: ComponentFixture<GiveawayItemComponent>;
      let dialog: any;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [
            GiveawayItemComponent,
            SpendTicketsDialogComponent,
            NumberFormatter
          ],
          imports: [
            MaterialModule,
            BrowserAnimationsModule,
            getTranslocoModule({})
          ],
          providers: [{provide: MatDialog, useValue: dialogMock}],
          schemas: [CUSTOM_ELEMENTS_SCHEMA]
        })
          .overrideModule(BrowserModule, {
            set: {entryComponents: [SpendTicketsDialogComponent]}
          })
          .compileComponents();
      }));
    
      beforeEach(() => {
        fixture = TestBed.createComponent(GiveawayItemComponent);
        component = fixture.componentInstance;
        component.giveaway = giveawaysMock[0];
        component.numberOfChances = 100;
        dialog = TestBed.inject(MatDialog);
        fixture.detectChanges();
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    
      describe('enterGiveaway', () => {
        it('should open the spend tickets dialog', async(() => {
          component.enterGiveaway();
          fixture.detectChanges();
          const spy = spyOn(dialog, 'open').and.callThrough();
    
          expect(spy).toBeCalledTimes(1);
        }));
      });
    });

但这会引发错误this.dialog.open is not a function

我实际上认为这两种解决方案都不正确,因为我需要检查调用enterGiveaway是否会打开SpendTicketsDialog。

那我该如何验证呢?

2 个答案:

答案 0 :(得分:1)

您对MatDialog的模拟还不够好。

providers: [{provide: MatDialog, useValue: dialogMock}],

由于dialogMock是一个类,因此您应该像这样使用它:

useValue: new dialogMock()

useClass: dialogMock

否则,您的模拟游戏将没有open方法。

提示总是用大写字母命名您的班级

现在让我们转到测试用例,并注意执行顺序

component.enterGiveaway();   <-------------------------  (1)         
...
const spy = spyOn(dialog, 'open').and.callThrough(); <-- (2)

expect(spy).toBeCalledTimes(1); <----------------------- (3)
  • (1)是执行modal.open()方法的位置。如果我们为对话框提供了正确的模拟,那么它将被执行而没有任何问题

  • (2)是监视对话框方法以计算调用时间的位置。 此间谍不会再被调用。 modal.open()已在步骤上执行 (1)

  • (3),由于(2)步骤中所述的原因,没有任何间谍呼唤您的测试,因此测试失败。

解决方案是不言自明的:在执行enterGiveaway()方法之前放置间谍程序:

  const spy = spyOn(dialog, 'open').and.callThrough();
  component.enterGiveaway();

jasmine.createSpyObj

还有另一种便捷的方法,可以使用MatDialog模拟openjasmine.createSpyObj方法。

let dialog: jasmine.SpyObj<MatDialog>;
...

{provide: MatDialog, useValue: jasmine.createSpyObj<MatDialog>(['open'])}

beforeEach(() => {
  ...
  dialog = TestBed.inject(MatDialog) as jasmine.SpyObj<MatDialog>;
  ...
});

it('should open the spend tickets dialog', async(() => {
  component.enterGiveaway();

  expect(dialog.open.calls.count()).toBe(1);
}));

答案 1 :(得分:1)

您可以尝试以下代码。只需在调用dialog.open = jest.fn();之后定义component.enterGiveaway();,然后验证expect(dialog.open).toBeCalledTimes(1);

describe('enterGiveaway', () => {
  it('should open the spend tickets dialog', async(() => {
    // setup
    dialog.open = jest.fn();

    // execute
    component.enterGiveaway();

    // verify
    expect(dialog.open).toBeCalledTimes(1);
  }));
});