Reputation: 43
The previous answers to this question do not resolve my issue.
I'm encountering a TypeError while testing a method in my Angular component that opens a dialog to delete a user. The error message states:
TypeError: Cannot read properties of undefined (reading 'push') at MatDialog.open (node_modules/@angular/material/fesm2022/dialog.mjs:598:26) at MatDialog.open (node_modules/@angular/material/fesm2022/dialog.mjs:598:26) at UserComponent.deleteUser (src/app/components/user/user.component.ts:108:35) at UserContext.apply (src/app/components/user/user.component.spec.ts:147:15) at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:369:28) at ProxyZoneSpec.onInvoke (node_modules/zone.js/fesm2015/zone-testing.js:2081:39) at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:368:34) at ZoneImpl.run (node_modules/zone.js/fesm2015/zone.js:111:43) at runInTestZone (node_modules/zone.js/fesm2015/zone-testing.js:216:38) at UserContext. (node_modules/zone.js/fesm2015/zone-testing.js:234:32) at at UserContext.apply (src/app/components/user/user.component.spec.ts:147:15) at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:369:28) at ProxyZoneSpec.onInvoke (node_modules/zone.js/fesm2015/zone-testing.js:2081:39) at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:368:34) at ZoneImpl.run (node_modules/zone.js/fesm2015/zone.js:111:43) at runInTestZone (node_modules/zone.js/fesm2015/zone-testing.js:216:38)
Here's the relevant portion of my UserComponent:
deleteUser(userId: number, userNickname: string): void {
const dialogRef = this.dialog.open(DeleteDialogComponent, {
height: '400px',
width: '550px',
data: { userId, userNickname },
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
const subscription = this.userService.deleteUser(userId).subscribe({
next: () => {
console.log("Ok!")
},
error: error => {
}
});
}
});
}
And here is my test setup:
describe('UserComponent', () => {
let component: UserComponent;
let fixture: ComponentFixture<UserComponent>;
let userServiceMock: jasmine.SpyObj<UserService>;
let routerMock: jasmine.SpyObj<Router>;
let dialogSpy: jasmine.SpyObj<MatDialog>;
beforeEach(async () => {
userServiceMock = jasmine.createSpyObj<UserService>('UserService', {
deleteUser: of(1)
});
routerMock = jasmine.createSpyObj<Router>('Router', ['navigate']);
dialogSpy = jasmine.createSpyObj('MatDialog', ['open']);
await TestBed.configureTestingModule({
imports: [UserComponent, MatDialogModule, BrowserAnimationsModule],
providers: [
{ provide: UserService, useValue: userServiceMock },
{ provide: Router, useValue: routerMock },
{ provide: MatDialog, useValue: dialogSpy }
]
}).compileComponents();
fixture = TestBed.createComponent(UserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should open modal and delete user', () => {
const userId = 1;
const userNickname = 'JohnSmith';
component.deleteUser(userId, userNickname);
const dialogRef = dialogSpy.open.calls.mostRecent().returnValue;
dialogRef.afterClosed = jasmine.createSpy().and.returnValue(of(true));
component.deleteUser(userId, userNickname);
expect(userServiceMock.deleteUser).toHaveBeenCalledWith(userId);
});
});
The line this.openDialogs.push(dialogRef); in the MatDialog implementation seems to be the source of the issue.
I suspect that the dialogRef is not being created correctly during the test, but I'm unsure how to resolve this.
How can I ensure that dialogRef is correctly mocked so that the openDialogs array is not undefined? Are there any adjustments I need to make to my test setup or the deleteUser method to avoid this error? Any insights or suggestions would be greatly appreciated!
Upvotes: 3
Views: 448
Reputation: 57986
You should spyOn open
method and return an object that contains the property afterClosed
, this will be a function which returns on observable.
We can use fakeAsync
and flush
to wait for the async observable to complete, then verify the result.
it('should open modal and delete user', fakeAsync(() => {
const userId = 1;
const userNickname = 'JohnSmith';
dialogSpy.open.and.returnValue({
afterClosed: () => of(true),
} as any);
component.deleteUser(userId, userNickname);
flush();
expect(userServiceMock.deleteUser).toHaveBeenCalledWith(userId);
}));
import {
TestBed,
ComponentFixture,
waitForAsync,
fakeAsync,
flush,
} from '@angular/core/testing';
import { AppComponent, UserService } from './app.component';
import { AppModule } from './app.module';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { of } from 'rxjs';
describe('AppComponent', () => {
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let userServiceMock: jasmine.SpyObj<UserService>;
let routerMock: jasmine.SpyObj<Router>;
let dialogSpy: jasmine.SpyObj<MatDialog>;
beforeEach(async () => {
userServiceMock = jasmine.createSpyObj<UserService>('UserService', {
deleteUser: of(1),
});
routerMock = jasmine.createSpyObj<Router>('Router', ['navigate']);
dialogSpy = jasmine.createSpyObj('MatDialog', ['open']);
await TestBed.configureTestingModule({
imports: [AppModule],
providers: [
{ provide: UserService, useValue: userServiceMock },
{ provide: Router, useValue: routerMock },
{ provide: MatDialog, useValue: dialogSpy },
],
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should open modal and delete user', fakeAsync(() => {
const userId = 1;
const userNickname = 'JohnSmith';
dialogSpy.open.and.returnValue({
afterClosed: () => of(true),
} as any);
component.deleteUser(userId, userNickname);
flush();
expect(userServiceMock.deleteUser).toHaveBeenCalledWith(userId);
}));
});
Upvotes: 0