Search code examples
angularunit-testingtestingspyonmat-pagination

Angular unit testing with Jest Mat paginator test case getting failed :- TypeError: Cannot read property 'page' of undefined


I have a component which uses Mat paginator for pagination purpose, the component is working fine but while running the command npx jest -- layout.component.spec.ts (I am are using jest framework)the below test case is getting failed with below is the error.

Test Case getting failed :

it("should create", () => {
        expect(component).toBeTruthy();
 });

ERROR:

should create (1070ms)

● LayoutComponent › should create

TypeError: Cannot read property 'page' of undefined



  at LayoutComponent.ngAfterViewInit (src/app/modules/adverse-media/card-layout/card-layout.component.ts:2015:20)
  at ngAfterViewInit (node_modules/@angular/core/bundles/core.umd.js:21557:22)
  at callProviderLifecycles (node_modules/@angular/core/bundles/core.umd.js:21531:17)
  at callElementProvidersLifecycles (node_modules/@angular/core/bundles/core.umd.js:21521:33)
  at callLifecycleHooksChildrenFirst (node_modules/@angular/core/bundles/core.umd.js:29563:9)
  at apply (node_modules/@angular/core/bundles/core.umd.js:30424:29)
  at Object.callWithDebugContext [as checkAndUpdateView] (node_modules/@angular/core/bundles/core.umd.js:30126:16)      at ViewRef_.checkAndUpdateView [as detectChanges] (node_modules/@angular/core/bundles/core.umd.js:20829:26)     
  at ComponentFixture._tick (../../packages/core/testing/src/component_fixture.ts:107:28)
  at ../../packages/core/testing/src/component_fixture.ts:120:36
  at ZoneDelegate.apply [as invoke] (node_modules/zone.js/dist/zone.js:391:26)
  at ProxyZoneSpec.invoke [as onInvoke] (node_modules/zone.js/dist/proxy.js:129:39)
  at ZoneDelegate.onInvoke [as invoke] (node_modules/zone.js/dist/zone.js:390:52)
  at Object.invoke [as onInvoke] (node_modules/@angular/core/bundles/core.umd.js:26371:37)
  at ZoneDelegate.onInvoke [as invoke] (node_modules/zone.js/dist/zone.js:390:52)
  at Zone.invoke [as run] (node_modules/zone.js/dist/zone.js:150:43)
  at NgZone.run (node_modules/@angular/core/bundles/core.umd.js:26285:32)
  at ComponentFixture.detectChanges (../../packages/core/testing/src/component_fixture.ts:120:19)
  at src/app/modules/adverse-media/card-layout/card-layout.component.spec.ts:55:13
  at ZoneDelegate.apply [as invoke] (node_modules/zone.js/dist/zone.js:391:26)
  at ProxyZoneSpec.invoke [as onInvoke] (node_modules/zone.js/dist/proxy.js:129:39)
  at ZoneDelegate.onInvoke [as invoke] (node_modules/zone.js/dist/zone.js:390:52)
  at Zone.invoke [as run] (node_modules/zone.js/dist/zone.js:150:43)

Component Code / usage of paginator:

@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;

ngAfterViewInit(): void {
    this.paginator.page.subscribe(() => {
      this.characterDatabase
        .getCharacters("", "", this.paginator.pageIndex)
        .subscribe((response: HttpRequest) => {
          this.characterDataSource = new MatTableDataSource(
            response.results as any[]
          );
          this.resultsLength = response.info.count;
          this.characters$ = this.characterDataSource.connect();
        });
    });
  }

Definition of page being used in the ngAfterViewInit here this.paginator.page.subscribe

 paginator.d.ts
   /** Event emitted when the paginator changes the page size or page index. */
    readonly page: EventEmitter<PageEvent>;

Spec.ts File Code :-

import { HttpClientTestingModule } from "@angular/common/http/testing";
import { ViewChild } from "@angular/core";
import { async, ComponentFixture, TestBed } from "@angular/core/testing";
import {
  MatCardModule,
  MatChipsModule,
  MatIconModule,
  MatPaginator,
  MatPaginatorModule,
  MatSortModule,
  MatTableModule,
  PageEvent,
} from "@angular/material";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { PipesModule } from "@app/shared/pipes/pipes.module";   
import { LayoutComponent } from "./card-layout.component";

describe("LayoutComponent", () => {
  let component: LayoutComponent;
  let fixture: ComponentFixture<LayoutComponent>;

  //let someServiceMock;

  beforeEach(async(() => {

    TestBed.configureTestingModule({
      declarations: [LayoutComponent],
      imports: [
        PipesModule,
        PipesModule,
        MatCardModule,
        MatChipsModule,
        MatPaginatorModule,
        MatIconModule,
        HttpClientTestingModule,
        MatTableModule,
        MatSortModule,
        BrowserAnimationsModule,
      ],
      providers: [{ provide: MatPaginator, useValue: MatPaginator }],
    }).compileComponents();
  }));

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

  it("should create", () => {
    expect(component).toBeTruthy();
  });
});

What I have tried till now to mock that read only page event emitter :-

 //let someServiceMock;
//const paginator = jasmine.createSpyObj('ChildComponent', ['childMethod']);
    //someServiceMock = {testEmitter: {subscribe: spyOn('testEmitter subscribe')}};

fixture = TestBed.createComponent(CardLayoutComponent);
    component = fixture.componentInstance;

    //Object.defineProperty(component.paginator.page, PageEvent , Writable);
    //spyOn(component.paginator.page, "subscribe").and.returnValue("");
    //spyOn(component.paginator, 'page', 'subscribe').and.returnValue(1);
    //expect(component.paginator.page.emit).toReturn();
    fixture.detectChanges();

it("should create", () => {
    //spyOn(component.paginator.page, "subscribe").and.returnValue("");
    expect(component).toBeTruthy();
  });

something that I have tried from my end, I was not able to post everything what I tried but a help is appreciated as I am much new to jest, i am not able to figure this out. Please let me know how to mock/define this item so that test should pass. sorry for typos


Solution

  • The line below is incorrect, it is doing nothing:

    providers: [{ provide: MatPaginator, useValue: MatPaginator }],
    

    What I think is happening, is that the pagination component (@ViewChild()) is being blocked by an *ngIf and therefore this.paginator does not exist in the ngAfterViewInit hook.

    For a super quick unblock, try this:

    ngAfterViewInit(): void {
        if (this.paginator) {
          // !! the rest of the logic here
        }
      }
    

    You should also try to find out why this.paginator is undefined (why MatPaginator is not in the HTML) when you unit test.