Angular 2 使用 sinon 测试组件 - 组件未定义
Angular 2 Testing Component with sinon - component is undefined
我正在尝试测试一个比 Angular 团队的 quickstart 教程简单(但看起来更复杂)的教程。我已经尝试查看了十几篇其他文章,但也许是由于过去 6 到 12 个月 ng2 框架的 "fluid" 性质,那里有相当多的资源现在看起来已经过时了。
我正在使用 Angular 2 版本 2.4.10、Typescript、Sinon 版本 2.1.0 和 Webpack 2(如果重要的话,还有 ag-grid)。
组件代码:
更新了问题以将 OtherService 包含在构造函数中,这是我第一次复制它时遗漏的问题
import { Component, OnInit } from "@angular/core";
import { GridOptions } from "ag-grid";
import { EventDataService } from "./data-service/event-data.service";
import { OtherService } from "./data-service/other.service";
import { ColumnDefs } from "../shared/event-grid/event-grid-column-defs";
import { Event } from "./event/event.interface";
import { Observable } from "rxjs/Rx";
@Component({
templateUrl: "./event-list.component.html",
styleUrls: ["./event-list.component.scss"]
})
export class EventListComponent implements OnInit {
// grid data & settings
private gridOptions: GridOptions;
private columnDefs: any;
// auto refresh / event-service messaging settings
public loadingEvents: boolean;
// main data array = holds event list displayed by grid
private events: Event[];
// updated question with privateOtherService, which I had missed when I first copied this over
constructor(private eventDataService: EventDataService, private otherService: OtherService) {
// initialize grid
this.gridOptions = <GridOptions>{};
this.gridOptions.columnDefs = ColumnDefs;
// initialize flag to indicate we are waiting for events from service
this.loadingEvents = false;
}
beginPollingLoop(): void {
this.getEvents();
// ... other logic that will repeat the getEvents call periodically...
};
getEvents() {
this.loadingEvents = true;
// treats response from event service as an observable to subscribe to
return this.eventDataService.getEvents()
.subscribe(
(events: Event[]) => { this.events = events; },
(error: any) => // handle errors,
() => {this.loadingEvents = false; });
};
ngOnInit(): void {
// begin polling loop (using defaults) once component loads
this.beginPollingLoop();
}
}
规格代码:
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { expect } from "chai";
import { spy } from "sinon";
import { EventListComponent } from "./event-list.component";
// dependency of component
import { EventDataService } from "./data-service/event-data.service";
import { AgGridModule } from "ag-grid-angular/main";
let component: EventListComponent;
let fixture: ComponentFixture<EventListComponent>;
let spies = {
beginPollingLoop: {}
};
let eventServiceStub = {
getEvents: Observable.from([])
};
describe("EventListComponent", () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [AgGridModule.withComponents([])],
declarations: [ EventListComponent ],
providers: [{ provide: EventDataService, useValue: eventServiceStub }]
});
fixture = TestBed.createComponent(EventListComponent);
component = fixture.componentInstance; // component is undefined
spies.beginPollingLoop = spy(component, "beginPollingLoop");
});
describe("EventListComponent", () => {
describe("When the component initializes", () => {
it("should set loadingEvents to 'false'", () => {
expect(component.loadingEvents).to.be.true;
});
});
describe("when OnInit() runs", () => {
let beginPollingLoop = spy(component, "beginPollingLoop");
expect(beginPollingLoop.called).to.equal(true);
});
});
});
我在控制台中遇到的具体错误是 "Uncaught TypeError: Cannot read property 'beginPollingLoop' of undefined"。根据调试器,component
是未定义的。在我的屏幕上,Karma 的测试输出错误信息是:"TypeError: Cannot read property 'injector' of null"
我觉得我正在按照文档的指示构建测试台,所以我只能假设我以某种方式没有正确注册我的组件的所有依赖项。这导致我的 component
无法正确创建。
谢谢!
我想通了。在规范中,component
是 "undefined",因为当 TestBed
试图创建 component
时,它失败了,因为我在 [=14] 的构造函数中使用了额外的服务=] 我没有在我的规格中考虑。一旦我意识到这一点并在我的规范中也删除了该服务,该组件就创建好了。
那是问题的根源 - 我只是没有看到它,因为抛出的错误 Angular 被埋在它自己的代码深处的某个地方。我没有看到像 "TestBed failed to create component: constructor argument mismatch" 这样有用的东西或有用的东西,而是看到与尝试使用我认为已成功创建的组件的其他东西相关的错误。这使得组件没有被创建变得不清楚,为什么它失败了:我正在创建的组件没有被提供它的构造函数期望的参数。
因此最终规格如下:
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { expect } from "chai";
import { spy } from "sinon";
import { EventListComponent } from "./event-list.component";
// dependency of component
import { EventDataService } from "./data-service/event-data.service";
import { OtherService } from "./data-service/other.service";
let component: EventListComponent;
let fixture: ComponentFixture<EventListComponent>;
let eventServiceStub = {
getEvents: Observable.from([])
};
// forgot this one
let otherServiceStub = {
getStuff: () => {}
};
describe("EventListComponent", () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [ EventListComponent ],
providers: [
{ provide: EventDataService, useValue: eventServiceStub },
{ provide: OtherDataService, useValue: otherServiceStub } // this was missing
]
});
fixture = TestBed.createComponent(EventListComponent);
component = fixture.componentInstance; // component is undefined
});
describe("EventListComponent", () => {
describe("when OnInit() runs", () => {
let beginPollingLoop = spy(component, "beginPollingLoop");
expect(beginPollingLoop.called).to.equal(true);
});
});
});
});
我正在尝试测试一个比 Angular 团队的 quickstart 教程简单(但看起来更复杂)的教程。我已经尝试查看了十几篇其他文章,但也许是由于过去 6 到 12 个月 ng2 框架的 "fluid" 性质,那里有相当多的资源现在看起来已经过时了。
我正在使用 Angular 2 版本 2.4.10、Typescript、Sinon 版本 2.1.0 和 Webpack 2(如果重要的话,还有 ag-grid)。
组件代码:
更新了问题以将 OtherService 包含在构造函数中,这是我第一次复制它时遗漏的问题
import { Component, OnInit } from "@angular/core";
import { GridOptions } from "ag-grid";
import { EventDataService } from "./data-service/event-data.service";
import { OtherService } from "./data-service/other.service";
import { ColumnDefs } from "../shared/event-grid/event-grid-column-defs";
import { Event } from "./event/event.interface";
import { Observable } from "rxjs/Rx";
@Component({
templateUrl: "./event-list.component.html",
styleUrls: ["./event-list.component.scss"]
})
export class EventListComponent implements OnInit {
// grid data & settings
private gridOptions: GridOptions;
private columnDefs: any;
// auto refresh / event-service messaging settings
public loadingEvents: boolean;
// main data array = holds event list displayed by grid
private events: Event[];
// updated question with privateOtherService, which I had missed when I first copied this over
constructor(private eventDataService: EventDataService, private otherService: OtherService) {
// initialize grid
this.gridOptions = <GridOptions>{};
this.gridOptions.columnDefs = ColumnDefs;
// initialize flag to indicate we are waiting for events from service
this.loadingEvents = false;
}
beginPollingLoop(): void {
this.getEvents();
// ... other logic that will repeat the getEvents call periodically...
};
getEvents() {
this.loadingEvents = true;
// treats response from event service as an observable to subscribe to
return this.eventDataService.getEvents()
.subscribe(
(events: Event[]) => { this.events = events; },
(error: any) => // handle errors,
() => {this.loadingEvents = false; });
};
ngOnInit(): void {
// begin polling loop (using defaults) once component loads
this.beginPollingLoop();
}
}
规格代码:
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { expect } from "chai";
import { spy } from "sinon";
import { EventListComponent } from "./event-list.component";
// dependency of component
import { EventDataService } from "./data-service/event-data.service";
import { AgGridModule } from "ag-grid-angular/main";
let component: EventListComponent;
let fixture: ComponentFixture<EventListComponent>;
let spies = {
beginPollingLoop: {}
};
let eventServiceStub = {
getEvents: Observable.from([])
};
describe("EventListComponent", () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [AgGridModule.withComponents([])],
declarations: [ EventListComponent ],
providers: [{ provide: EventDataService, useValue: eventServiceStub }]
});
fixture = TestBed.createComponent(EventListComponent);
component = fixture.componentInstance; // component is undefined
spies.beginPollingLoop = spy(component, "beginPollingLoop");
});
describe("EventListComponent", () => {
describe("When the component initializes", () => {
it("should set loadingEvents to 'false'", () => {
expect(component.loadingEvents).to.be.true;
});
});
describe("when OnInit() runs", () => {
let beginPollingLoop = spy(component, "beginPollingLoop");
expect(beginPollingLoop.called).to.equal(true);
});
});
});
我在控制台中遇到的具体错误是 "Uncaught TypeError: Cannot read property 'beginPollingLoop' of undefined"。根据调试器,component
是未定义的。在我的屏幕上,Karma 的测试输出错误信息是:"TypeError: Cannot read property 'injector' of null"
我觉得我正在按照文档的指示构建测试台,所以我只能假设我以某种方式没有正确注册我的组件的所有依赖项。这导致我的 component
无法正确创建。
谢谢!
我想通了。在规范中,component
是 "undefined",因为当 TestBed
试图创建 component
时,它失败了,因为我在 [=14] 的构造函数中使用了额外的服务=] 我没有在我的规格中考虑。一旦我意识到这一点并在我的规范中也删除了该服务,该组件就创建好了。
那是问题的根源 - 我只是没有看到它,因为抛出的错误 Angular 被埋在它自己的代码深处的某个地方。我没有看到像 "TestBed failed to create component: constructor argument mismatch" 这样有用的东西或有用的东西,而是看到与尝试使用我认为已成功创建的组件的其他东西相关的错误。这使得组件没有被创建变得不清楚,为什么它失败了:我正在创建的组件没有被提供它的构造函数期望的参数。
因此最终规格如下:
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { expect } from "chai";
import { spy } from "sinon";
import { EventListComponent } from "./event-list.component";
// dependency of component
import { EventDataService } from "./data-service/event-data.service";
import { OtherService } from "./data-service/other.service";
let component: EventListComponent;
let fixture: ComponentFixture<EventListComponent>;
let eventServiceStub = {
getEvents: Observable.from([])
};
// forgot this one
let otherServiceStub = {
getStuff: () => {}
};
describe("EventListComponent", () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [ EventListComponent ],
providers: [
{ provide: EventDataService, useValue: eventServiceStub },
{ provide: OtherDataService, useValue: otherServiceStub } // this was missing
]
});
fixture = TestBed.createComponent(EventListComponent);
component = fixture.componentInstance; // component is undefined
});
describe("EventListComponent", () => {
describe("when OnInit() runs", () => {
let beginPollingLoop = spy(component, "beginPollingLoop");
expect(beginPollingLoop.called).to.equal(true);
});
});
});
});