Unit testing angular 5 component with @ViewChild

Jacopo Lanzoni picture Jacopo Lanzoni · Jul 25, 2018 · Viewed 26k times · Source

I am using angular 5.2.0. I have a child component

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

@Component({
    template: `<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>`
})
export class ParentComponent {

    @ViewChild('child')
    public 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.

Answer

Anuradha Gunasekara picture Anuradha Gunasekara · Jul 25, 2018

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();
    });

});