Search code examples
angularunit-testingjasminekarma-jasmineangular15

Unit test returns Cannot read property 'open' of undefined message


I am codng ia Unit Test on a code and component working. I can't understand what's going on. I have tried different approaches but I always get the same error. I have simplified the Code of the Unit Test to the maximum so that you can see the problem more easily. Thank you

COMPONENT

openUpdate(rowSelected: iData) {
        let dialogRef = this.dialog.open(DialogEditComponent, {});
        dialogRef.componentInstance.dialogRef = dialogRef;
        dialogRef.componentInstance.selectedData = rowSelected;
        const sub = dialogRef.componentInstance.onAdd.subscribe((data: iData) => {
            if (data) {
                this.update(data);
            }
        });
        dialogRef.afterClosed().subscribe(() => {
            sub.unsubscribe();
        });
    }

SPEC.TS

beforeEach(async () => {
    const dialogSpy = {
      open: jasmine.createSpy('open').and.returnValue({
        componentInstance: {
          onAdd: jasmine.createSpyObj('onAdd', ['subscribe'])
        },
        afterClosed: () => {
          return jasmine.createSpyObj('afterClosed', ['subscribe']);
        }
      })
    };
    toastrServiceSpy = jasmine.createSpyObj('ToastrService', ['success', 'error']);

    await TestBed.configureTestingModule({
      declarations: [Component1, DialogEditComponent],
      imports: [MatDialogModule, BrowserAnimationsModule],
      providers: [
        { provide: MatDialog, useValue: dialogSpy },
        { provide: ToastrService, useValue: toastrServiceSpy },
        { provide: MAT_DIALOG_DATA, useValue: {} },
      ],
    }).compileComponents();
  });

........

it('should open the edit dialog with the selected data', async () => {
    await component.openUpdate(mockData[0]);
    expect(dialogSpy.open).toHaveBeenCalled();
  });

ERROR

TypeError: Cannot read property 'open' of undefined
        at UserContext.<anonymous> (src/app/features/component1.component.spec.ts:99:22)
        at Generator.next (<anonymous>)
        at asyncGeneratorStep (node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js:3:1)
        at apply (node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js:22:1)
        at _ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:409:30)
        at ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/zone-testing.js:303:43)
        at _ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:408:56)
        at Zone.run (node_modules/zone.js/dist/zone.js:169:47)
        at apply (node_modules/zone.js/dist/zone.js:1326:38)
        at _ZoneDelegate.invokeTask (node_modules/zone.js/dist/zone.js:443:35)

Solution

  • When you see the issue in the .spec.ts file like so, it means open is undefined in your unit test (not component file so the mocking is most likely done properly).

    TypeError: Cannot read property 'open' of undefined
            at UserContext.<anonymous> (src/app/features/component1.component.spec.ts:99:22)
    

    I bet the issue is the way you declare dialogSpy.

    Do the following (pay attention to comments with !!):

    // !! Declare dialogSpy outside of the first `beforeEach` so all `it` blocks
    // will have access to it.
    let dialogSpy: any;
    
    beforeEach(async () => {
        // !! Remove const here and assign dialogSpy to what it should be
        dialogSpy = {
          open: jasmine.createSpy('open').and.returnValue({
            componentInstance: {
              onAdd: jasmine.createSpyObj('onAdd', ['subscribe'])
            },
            afterClosed: () => {
              return jasmine.createSpyObj('afterClosed', ['subscribe']);
            }
          })
        };
        toastrServiceSpy = jasmine.createSpyObj('ToastrService', ['success', 'error']);
    
        await TestBed.configureTestingModule({
          declarations: [Component1, DialogEditComponent],
          imports: [MatDialogModule, BrowserAnimationsModule],
          providers: [
            { provide: MatDialog, useValue: dialogSpy },
            { provide: ToastrService, useValue: toastrServiceSpy },
            { provide: MAT_DIALOG_DATA, useValue: {} },
          ],
        }).compileComponents();
      });
    
    it('should open the edit dialog with the selected data', async () => {
        await component.openUpdate(mockData[0]);
        // !! Log out dialogSpy and make sure it is not undefined
        console.log(dialogSpy);
        expect(dialogSpy.open).toHaveBeenCalled();
      });