Hi im doing unit testing and trying to get coverage for my subscription. However it keeps failing with the error dialogRef.afterClosed is not a function
Im not sure at what or how I can spy on the afterClosed() function, but thats the only part I cant seem to cover. ...
import { MatDialog } from '@angular/material/dialog';
....
constructor( private dialog: MatDialog) { }
showLogin(): void {
const dialogRef = this.dialog.open(LoginDialogComponent, {
width: '400px',
height: 'auto',
data: this.loginContext
});
dialogRef.afterClosed().subscribe(result => {
if (this.loginContext.loggedIn) {
this.showApply();
}
});
}
this is the LoginDialogComponent
....
@Component({
selector: 'app-login-dialog',
templateUrl: './login-dialog.component.html',
styleUrls: ['./login-dialog.component.scss']
})
export class LoginDialogComponent implements OnInit {
constructor(
private authService: AuthService,
private alertService: AlertService,
public dialogRef: MatDialogRef<LoginState>,
@Inject(MAT_DIALOG_DATA) public data: LoginState) { }
ngOnInit() {
this.data.loggedIn = false;
}
login(loginEvent: LoginEvent): void {
this.authService.login(loginData).then(
resp => {
this.data.loggedIn = true; // feedback
this.dialogRef.close();
},
err => {
this.alertService.showAlert(err);
}
);
}
}
I have taken Sam Tsai's answer one step further and created a stub file to be more concise. Here's the stub file contents:
import { of } from 'rxjs';
/*
The default behavior is to test that the user clicked 'OK' in the dialog.
To reset to this behavior (if you've previously tested 'Cancel'),
call setResult(true).
If you want to test that the user clicked 'Cancel' in the dialog,
call setResult(false).
*/
export class MatDialogStub {
result: boolean = true;
setResult(val: boolean) {
this.result = val;
}
open() {
return {afterClosed: () => of(this.result) };
}
}
Then in the .spec.ts file you use the stub like this:
import { MatDialogStub } from '../../../../testing/mat-dialog-stub'; // <--- (import here)
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
const dialogStub = new MatDialogStub(); // <--- (declare here)
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [ MyComponent ],
providers: [
{ provide: MatDialog, useValue: dialogStub } // <--- (use here)
]
})
.compileComponents();
}));
//...
});
And in the actual test you can set the return value to true or false to simulate clicking the 'OK' or 'Cancel' button respectively by calling the setResult() function:
dialogStub.setResult(true);
or
dialogStub.setResult(false);
Note: the default value for the stub tests 'OK' so you don't have to call the function if you are just testing 'OK'.
The tests below first simulate a 'Cancel' and then an 'OK' button click:
it(`should not call 'delete' when Submit button pressed and user cancels`, async(() => {
component.apis = [new Api({name: 'abc'})];
component.displayPermissions = [new Permission({name: 'abc'})];
dialogStub.setResult(false); // <--- (call the function here)
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
compiled.querySelector('button').click();
expect(permissionService.delete.calls.count()).toBe(0, 'no calls');
}));
it(`should call 'delete' once when Submit button pressed and not cancelled`, async(() => {
component.apis = [new Api({name: 'abc'})];
component.displayPermissions = [new Permission({name: 'abc'})];
dialogStub.setResult(true); // <--- (call the function here)
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
compiled.querySelector('button').click();
expect(permissionService.delete.calls.count()).toBe(1, 'one call');
}));
Originial Answer
I have looked everywhere for a concise way to mock the MatDialog and get rid of the error I was getting dialogRef.afterClosed is not a function. (see some links I tried at bottom). Nothing I found was the right solution until I tried Sam Tsai's answer to this question. It was a simple solution that got rid of all my errors and enabled me to correctly test my application. I wish I found this before spending so much time on all the other links.
This is the unit test that was failing; it failed on the ('button').click event because it opens the MatDialog and only performs the delete if the dialog returns true. I didn't know how to mock the MatDialog correctly so the delete would happen:
it("should call 'delete' once when Submit button pressed and not cancelled", async(() => {
component.apis = [new Api({name: 'abc'})];
component.displayPermissions = [new Permission({name: 'abc'})];
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
compiled.querySelector('button').click();
expect(permissionService.delete.calls.count()).toBe(1, 'one call');
}));
I fixed it by adding this:
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
const dialogRefStub = {
afterClosed() {
return of(true);
}
};
const dialogStub = { open: () => dialogRefStub };
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [ MyComponent ],
providers: [
{ provide: MatDialog, useValue: dialogStub }
]
})
.compileComponents();
}));
//...
});
Some links I tried:
You can stub the Dialog
and its DialogRef
return value.
I do:
const dialogRefStub = {
afterClosed() {
return of('result'); // this can be whatever, esp handy if you actually care about the value returned
}
};
const dialogStub = { open: () => dialogRefStub };
And then I add to the providers:
{
provide: MatDialog,
useValue: dialogStub
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With