类似于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。
那我该如何验证呢?
答案 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();
还有另一种便捷的方法,可以使用MatDialog
模拟open
和jasmine.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);
}));
});