Reputation: 5562
Similar to this question, but it doesn't provide an answer that works for me.
I have a simple component that has a method that opens a dialog:
enterGiveaway() {
this.dialog.open(SpendTicketsDialogComponent, {
width: '370px',
height: '600px'
});
}
For now I just want to test that calling that method results in the dialog being opened.
The test fails with this error:
expect(spy).toBeCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
with this code:
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);
}));
});
});
I understand of course, that MatDialog is not referencing the actual SpendTicketsDialogComponent
which is the one that is opened. So I tried providing a mock object for the dialog:
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);
}));
});
});
but this throws the error this.dialog.open is not a function
.
I actually don't think either solution is correct, because I need to check that calling enterGiveaway opens the SpendTicketsDialog.
So how can I verify that?
Upvotes: 5
Views: 13212
Reputation: 214175
Your mock for MatDialog
is not good enough.
providers: [{provide: MatDialog, useValue: dialogMock}],
Since dialogMock
is a class then you should either use it like:
useValue: new dialogMock()
or
useClass: dialogMock
Otherwise your mock won't have open
method.
Tip: always name your classes with Capital letter
Now let's go to your test case and pay attention on the execution order:
component.enterGiveaway(); <------------------------- (1)
...
const spy = spyOn(dialog, 'open').and.callThrough(); <-- (2)
expect(spy).toBeCalledTimes(1); <----------------------- (3)
(1)
is where modal.open()
method is being executed. If we provided corrected mock for dialog then it will be executed without any problems
(2)
is where you spy on dialog method in order to count call times. This spy won't be called later. modal.open() was already executed at step (1)
(3)
your test fails since there wasn't any calles for your spy for the reasons described in (2)
step.
The solution is quite self-explanatory: put spy procedure before executing enterGiveaway()
method:
const spy = spyOn(dialog, 'open').and.callThrough();
component.enterGiveaway();
There is another handy way of how to mock your MatDialog
and open
method by using 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);
}));
Upvotes: 7
Reputation: 2864
You can try below code. Simply define dialog.open = jest.fn();
after that call component.enterGiveaway();
then verfiy 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);
}));
});
Upvotes: 2