21

I am opening my mat-dialog with the following function:

accept() {
  let dialogRef = this.dialog.open(AcceptDialogComponent, {
    data: {
      hasAccepted: false
    }
  })
  dialogRef.afterClosed().subscribe(result => {
    console.log(result);
    if (result.hasAccepted === true) {
      this.leadService.acceptLead(this.holdingAccountId, this.lead.id)
        .pipe(
          takeUntil(this.onDestroy$)
        )
        .subscribe(acceptLeadRes => {
            console.log(acceptLeadRes);
            this.leadService.updateLeadAction('accept');
          },
          (err: HttpErrorResponse) => {
            console.log(err);
            this.router.navigate(['/error']);
          });
    }
  });
}

I am attempting to write a test for this function that simply fires the afterClosed() so that I can check if my service method that makes a backend call is called.

component.spec.ts (beforeEach Testbed creation)

beforeEach(async (() => {
  TestBed.configureTestingModule({
      declarations: [LeadCardComponent, AcceptDialogComponent],
      imports: [
        requiredTestModules,
        JwtModule.forRoot({
          config: {
            tokenGetter: () => {
              return '';
            }
          }
        })
      ],
      providers: [
        ApplicationInsightsService,
        JwtHelperService,
        // { provide: LeadsService, useValue: leadServiceSpy }
      ],
    }),

    TestBed.overrideModule(BrowserDynamicTestingModule, {
      set: {
        entryComponents: [AcceptDialogComponent]
      }
    });
  TestBed.compileComponents();
}));

component.spec.ts (test)

it('Return from AcceptLeadDialog with hasAccepted equals true should call acceptLead endpoint', () => {
  let matDiaglogref = dialog.open(AcceptDialogComponent, {
    data: {
      hasAccepted: false
    }
  });
  spyOn(matDiaglogref, 'afterClosed').and.callThrough().and.returnValue({
    hasAccepted: true
  });
  spyOn(leadService, 'acceptLead').and.callThrough();
  component.acceptLead();
  fixture.detectChanges();
  matDiaglogref.close();
  fixture.detectChanges();

  expect(leadService.acceptLead).toHaveBeenCalled();
});

The test currently fails with a "Expected spy acceptLead to have been called." I am failing on understanding how to test the function and execute some sort of mock MatDialogRef so that I can check if the conditions of my test pass.

Any help/suggestions would be much appreciated

Update Working Test implemented from Accepted Answer

it('Return from AcceptLeadDialog with hasAccepted equals true should call acceptLead endpoint', () => {
  spyOn(component.dialog, 'open')
    .and
    .returnValue({
      afterClosed: () => of({
        hasAccepted: true
      })
    });
  spyOn(leadService, 'acceptLead').and.callThrough();
  component.acceptLead();
  expect(component.dialog).toBeDefined();
  expect(leadService.acceptLead).toHaveBeenCalled();
});
Magiczne
  • 1,586
  • 2
  • 15
  • 23
Brian Stanley
  • 2,078
  • 5
  • 24
  • 43

4 Answers4

42

I solved this problem as the first post by @Adithya Sreyaj but added the next change:

spyOn(component.dialog, 'open')
    .and
    .returnValue({
        afterClosed: () => of(true)
    } as MatDialogRef<typeof component>);
Magiczne
  • 1,586
  • 2
  • 15
  • 23
CrgioPeca88
  • 973
  • 7
  • 12
32

You can test Angular Material Dialog's afterClosed method this way:

  1. import { of } from 'rxjs';
  2. spy on the dialog and return an observable for the afterClosed() method
spyOn(component.dialog, 'open')
     .and
     .returnValue({afterClosed: () => of(true)});

Basically, the afterClosed() of the dialogRef is expecting an Observable. So we spy on the component's dialog open method and return an Observable for the afterClosed() method by using the of operator from rxjs.

You can then replace the of(true) from the returnValue with your own data what you are sending in the afterClosed() of the dialog in the main component.

Adithya Sreyaj
  • 1,710
  • 10
  • 22
  • 6
    it is throwing error. can you help me with that. Argument of type '{ afterClosed: () => Observable; }' is not assignable to parameter of type 'MatDialogRef'. Type '{ afterClosed: () => Observable; }' is missing the following properties from type 'MatDialogRef': _overlayRef, _containerInstance, id, componentInstance, and 18 more.t – Sunil Soni Jun 10 '20 at 07:16
  • @SunilSoni, I am facing the same problem. Did you solve it? – Rod Nolan Nov 19 '21 at 21:15
  • 3
    @SunilSoni Well `.open` is normally supposed to return a `MatDialogRef`. In this case you are only returning a fraction of that object, so TypesScript is complaining about it. To satisfy TypeScript you can cast the value using: `.and.returnValue({ afterClosed: () => of(data) } as MatDialogRef)`. But be aware that TypeScript complains were quite valid. If calls to any other method of `MatDialogRef` are made within the test, it's going to fail trying to call `undefined`. – Elias Dec 16 '21 at 07:26
  • @RodNolan see my other comment on this question please. – Elias Dec 16 '21 at 07:27
  • 1
    Thanks @Elias for your help – Sunil Soni Jan 04 '22 at 13:36
4

I think you are missing the whole point of unit testing the component. From my understanding:

  1. You have a function accept() which creates subscription to the closing event of this.dialog
  2. You should write unit test to verify the logic that the subscription is getting created and the service is called.
  3. Make dialogRef global to component rather than keeping it private to accept(). This would help you test your code better. private variables can't be access during unit testing.

So:

component.ts

accept() {
 this.dialogRef = this.dialog.open(AcceptDialogComponent, {
  data: {
    hasAccepted: false
      }
    })
 this.dialogRef.afterClosed().subscribe(result => {
  console.log(result);
  if (result.hasAccepted === true) {
    this.leadService.acceptLead(this.holdingAccountId, this.lead.id)
    .pipe(
      takeUntil(this.onDestroy$)
    )
    .subscribe (acceptLeadRes => {
      console.log(acceptLeadRes);
      this.leadService.updateLeadAction('accept');
    },
    (err: HttpErrorResponse) => {
      console.log(err);
      this.router.navigate(['/error']);
    });
   }
 });
}

spec.ts

it('should create subscription to Modal closing event and call "acceptLead()" of "leadService" ', () => {
    spyOn(component.dialogRef, 'afterClosed').and.returnValue(
        of({
            hasAccepted: false
        })
    );
    spyOn(component.leadService, 'acceptLead').and.callThrough();
    component.accept();
    expect(component.dialogRef).toBeDefined();
    expect(component.dialogRef.afterClosed).toHaveBeenCalled();
    expect(component.leadService.acceptLead).toHaveBeenCalled();
});


Shashank Vivek
  • 16,888
  • 8
  • 62
  • 104
  • Sorry for the delay. I am not able to get your solution working. I could make dialogRef global to the component but I am opening several different dialogs from this component, would this not conflict with being able to do that? – Brian Stanley May 14 '19 at 16:28
  • @BrianStanley : If you are opening several `dialogRef`, the you'll have to make separate `refs` for all of them globally. If you keep the variable as private, you wont be able to test it using `jasmine` – Shashank Vivek May 16 '19 at 11:22
0

has anyone used the dialog, passing a callback function but instead of in the afterClosed the callback is in onOk method?

ex.

 let modalOptions = {
  uContent: 'are you sure you want to delete',
  uTitle: 'modal title',
  uOnOk: () => {
   callback function here
  }
};
this.modalService.create(modalOptions);

looking to test using jest the callback function

ArbolDie
  • 13
  • 1
  • 5