0

I set up a Jasmine spy to test that when the user clicks a button (triggering the goBack() function), it opens a dialog component, or as far as the test is concerned, calls a thing. It's a really simple test, and I have virtually identical tests that work, but this one doesn't for some reason. The output of the test says Expected spy open to have been called.

component-with-a-button.component.ts

import { MatDialog } from '@angular/material/dialog';

export class ComponentWithAButton {
    constructor(private closeDialog: MatDialog) {}
    // A button triggers the following function
    goBack(): void {
        this.closeDialog.open(CloseDialogComponent);
    }
}

component-with-a-button.component.spec.ts

import { MatDialog } from '@angular/material/dialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentWithAButton } from './component-with-a-button.component';

const closeDialogMock = { open: () => {} };

describe('ComponentWithAButton', () => {
    let component: ComponentWithAButton;
    let fixture: ComponentFixture<ComponentWithAButton>;

    beforeEach(async () => {
        await TestBed.configureTestingModule({
            declarations: [ComponentWithAButton],
            providers: [
                {
                    provide: MatDialog,
                    useValue: closeDialogMock,
                }
            ],
        }).compileComponents();
    });

    beforeEach(() => {
        fixture = TestBed.createComponent(ComponentWithAButton);
        component = fixture.componentInstance;
        fixture.detectChanges();
    });

    describe('goBack', () => {
        it('should call closeDialog.open', () => {
            const closeDialogSpy = spyOn(closeDialogMock, 'open');
            component.goBack();
            expect(closeDialogSpy).toHaveBeenCalled();
        });
    });
});
PaulBunion
  • 346
  • 2
  • 18
  • make sure you import `MatDialog` from correct path on your .spec file – Stavm Nov 18 '21 at 20:25
  • @Stavm oh it's in there. I'll update the post with imports too. I was just assuming those for simplicity. – PaulBunion Nov 18 '21 at 20:26
  • to me your code seems 100% valid and should work. can you change MatDialog to a different service, any service, and see if that works ? I'm ignoring the asunc typo in your `beforeEach` of course – Stavm Nov 18 '21 at 20:30
  • @Stavm see, that's what I don't get either. It looks just fine, but I've spent all day on this. Like I said, I've even got very similar tests (different file/component) that pass just fine. I tried swapping the MatDialog out with a different service, but it still doesn't pass. – PaulBunion Nov 18 '21 at 20:36
  • odd, can you instantly return a value and retry ? `spyOn(closeDialogMock, 'open').and.returnValue({})` – Stavm Nov 18 '21 at 20:42
  • I even added another test that does the exact same thing, but for another function (different button action) in the component. That test also passed fine. – PaulBunion Nov 18 '21 at 20:43
  • I tried adding `returnValue` and it didn't do anything as far as I could tell. It certainly didn't resolve the error. – PaulBunion Nov 18 '21 at 21:08
  • beforeEach(asunc () => { should be "async" – Winnemucca Nov 18 '21 at 22:59
  • @Winnemucca it is in my actual code (auto-generated boilerplate stuff). I didn't copy/paste the code, so it's just a typo here, but I'll still go ahead and edit that. – PaulBunion Nov 19 '21 at 15:48
  • I am just curious. Assuming you are using Karma and not jest right? if you put a breakpoint on `this.closeDialog.open(CloseDialogComponent);` You are getting to this point right? – Winnemucca Nov 19 '21 at 18:33
  • I'm not entirely sure what the configuration is. It was all set up before I got this job. Anyways, I know it worked because I was using that button on my localhost and it behaved as expected. The issue was more with the test. I've also just posted an answer below. – PaulBunion Nov 19 '21 at 18:40

1 Answers1

-1

As it turns out, jasmine won't let you use two providers of the same type (well, theoretically you should be able to add the multi: true flag to use multiple of the same provider, but it wasn't working for me, so that's something I need to do more reading on), and the final one will take precedence. In the question, I simplified my actual code to focus on where I thought the problem lay. My actual component code looked more like this (notice two MatDialogs instead of one):

import { MatDialog } from '@angular/material/dialog';
import { CloseDialogComponent } from 'some/direcotry/close-dialog.component';
import { SubmitDialogComponent } from '/other/directory/submit-dialog.component';

export class ComponentWithAButton {
    constructor(private closeDialog: MatDialog, private submitDialog: MatDialog) {}

    goBack(): void {
        this.closeDialog.open(CloseDialogComponent);
    }

    submit(): void {
        this.submitDialog.open(SubmitDialogComponent);
    }
}

To resolve the issue of having multiple providers of one type, I reduced the MatDialogs in the constructor to one and wrote a dialog builder function that takes a generic component, so now the component looks more like this:

import { MatDialog } from '@angular/material/dialog';
import { ComponentType } from '@angular/cdk/portal';
import { CloseDialogComponent } from 'some/direcotry/close-dialog.component';
import { SubmitDialogComponent } from '/other/directory/submit-dialog.component';

export class ComponentWithAButton {
    constructor(private dialogConstructor: MatDialog) {}

    goBack(): void {
        this.constructDialog(CloseDialogComponent);
    }

    submit(): void {
        this.constructDialog(SubmitDialogComponent);
    }

    private constructDialog(component: ComponentType<any>) {
        this.dialogConstructor.open(component);
    }
}
PaulBunion
  • 346
  • 2
  • 18