dialogRef.afterClosed is not a function

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:

  • Mocking Angular Material Dialog afterClosed() for unit test
  • How do I write simple test to check the behaviour of MatDialog in Angular 2?
  • Cannot read property 'afterClosed' of undefined when unit testing MatDialog in Jasmine
  • Failed: Can't resolve all parameters for MatDialogRef: (?, ?, ?). unit testing Angular project
  • How do I unit-test that MatDialog is closed, by mat-dialog-close or otherwise

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
  }