angularunit-testingviewchildtestbed

Unit testing angular 5 component with @ViewChild


I am using angular 5.2.0. I have a child component

import { Component } from '@angular/core';

@Component({
  template: `<div><div></div></div>`,
})
export class ChildComponent {

  public childMethod() {
    ...
  }
}

and a parent component which accesses the child via ViewChild

import { Component, ViewChild } from "@angular/core";
import { ChildComponent } from "child.component";

@Component({
  template: `
  <child-component #child>
    <child-component></child-component>
  </child-component>
  `,
})
export class ParentComponent {
  @ViewChild("child") child: ChildComponent;

  public parentMethod() {
    this.child.childMethod();
  }
}

I want a unit test proving that an invocation of parentMethod causes an invocation of childMethod. I have the following:


import { NO_ERRORS_SCHEMA } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { ChildComponent } from "./child.component";
import { ParentComponent } from "./parent.component";

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

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [ParentComponent, ChildComponent],
      schemas: [NO_ERRORS_SCHEMA],
    }).compileComponents();
  });

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

  it("should invoke childMethod when parentMethod is invoked", () => {
    const childMethodSpy: jasmine.Spy = spyOn(component.child, "childMethod");
    component.parentMethod();
    expect(childMethodSpy).toHaveBeenCalled();
  });
});

Yet, this does not work, and I get Error: <spyOn> : could not find an object to spy upon for childMethod().

Moreover, this is not a unit test, because I use the real ChildComponent instead of a mock. I tried creating a MockChildComponent and adding it to declarations and export but I got the same result. Any help?

I know there are similar post, but they are for different versions of angular, and they did not help.


Solution

  • You can do something like this.

    Create a spy object for the ChildComponent like this.

    const childComponent = jasmine.createSpyObj('ChildComponent', ['childMethod']);
    

    Then in the test, set the component's childComponent property to the spy that you have created.

    component.childComponent =  childComponent;
    

    Your test file should look like this.

    import { NO_ERRORS_SCHEMA } from "@angular/core";
    import { ComponentFixture, TestBed } from "@angular/core/testing";
    import { ChildComponent } from "./child.component";
    import { ParentComponent } from "./parent.component";
    
    describe("ParentComponent", () => {
      let component: ParentComponent;
      let fixture: ComponentFixture<ParentComponent>;
    
      const childComponent = jasmine.createSpyObj("ChildComponent", [
        "childMethod",
      ]);
    
      beforeEach(() => {
        TestBed.configureTestingModule({
          declarations: [ParentComponent, ChildComponent],
          schemas: [NO_ERRORS_SCHEMA],
        }).compileComponents();
      });
    
      beforeEach(() => {
        fixture = TestBed.createComponent(ParentComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      it("should invoke childMethod when parentMethod is invoked", () => {
        component.childComponent = childComponent;
        component.parentMethod();
        expect(childComponent.childMethod).toHaveBeenCalled();
      });
    });