使用 @ViewChild 对 angular 5 个组件进行单元测试

Unit testing angular 5 component with @ViewChild

我正在使用 angular 5.2.0。我有一个子组件

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

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

  public childMethod() {
    ...
  }
}

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

我想要一个单元测试来证明 parentMethod 的调用会导致 childMethod 的调用。我有以下内容:


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

然而,这不起作用,我得到 Error: <spyOn> : could not find an object to spy upon for childMethod()

此外,这不是单元测试,因为我使用的是真正的 ChildComponent 而不是模拟。我尝试创建一个 MockChildComponent 并将其添加到 declarationsexport 但我得到了相同的结果。有帮助吗?

我知道有类似的 post,但它们是针对不同版本的 angular,它们没有帮助。

你可以这样做。

像这样为 ChildComponent 创建一个间谍对象。

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

然后在测试中,将组件的childComponent属性设置为你创建的spy

component.childComponent =  childComponent;

您的测试文件应如下所示。

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


您可以模拟 ChildComponent,这样您就可以单独测试 ParentComponent,而不需要手动分配 ChildComponent。

如果 ParentComponent.childComponent 是私有的,这是必需的,如果 ChildComponent 有很多依赖项,这几乎是必需的。

您的测试文件应如下所示:

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

学分: 来自 Angular inDepth 的想法。 基于@Anuradha Gunasekara 的回答。

或者,覆盖此类代码的快速简便方法....

如果 .ts 文件是这样的:

@ViewChild('datePicker') myDatePicker: AngularMyDatePickerDirective

然后在 spec.ts 文件里面的 it() 块中,你可以添加如下内容:

component.myDatePicker = { toggleCalender: () => Promise.resolve()} as unknown as AngularMyDatePickerDirective;