'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 1:[1]
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();
});
});
Solution 2:[2]
Alternatively, quick and easy way of covering such code....
if the .ts file is like:
@ViewChild('datePicker') myDatePicker: AngularMyDatePickerDirective
then in spec.ts file inside it() block you can add something like:
component.myDatePicker = { toggleCalender: () => Promise.resolve()} as unknown as AngularMyDatePickerDirective;
Solution 3:[3]
You can mock ChildComponent, so you can test ParentComponent in isolation and don't need to assign ChildComponent manually.
This is required if ParentComponent.childComponent is private and it is almost required if ChildComponent has many dependencies.
Your test file should look like this:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ChildComponent } from './child.component';
import { ParentComponent } from './parent.component';
const childMethodSp? = jasmine.createSpy('childMethod');
class ChildStubComponent {
childMethod = childMethodSp?;
}
@Component({
selector: 'child-component',
template: '',
providers: [{ provide: ChildComponent, useClass: ChildStubComponent }]
})
describe('ParentComponent', () => {S
let component: ParentComponent;
let fixture: ComponentFixture<ParentComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ ParentComponent, ChildStubComponent ]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ParentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should invoke childMethod when parentMethod is invoked', () => {
component.parentMethod();
expect(childMethodSp?).toHaveBeenCalled();
});
});
Credits: Idea from Angular inDepth. Based on @Anuradha Gunasekara's answer.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Shadowalker |
Solution 2 | Luther |
Solution 3 |