#StackBounty: #angular #unit-testing #angular-material #mocking #jestjs Angular Jest Testing a component that opens a MatDialog – open …

Bounty: 100

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?


Get this bounty!!!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.