你如何测试调用服务的 Angular 2 指令?
How do you test Angular 2 directive that calls a service?
我使用 angular-cli 创建了一个简单的应用程序来说明我的问题。你可以在这里看到所有的代码:
https://github.com/wholladay/tracking
每当单击包含元素时,指令都会调用服务。因此,我想模拟该服务并确保在向指令发送点击事件时调用它。
这是我的测试代码:
/* tslint:disable:no-unused-variable */
import { inject, addProviders } from '@angular/core/testing';
import { TestComponentBuilder } from '@angular/compiler/testing';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { TrackingDirective } from './tracking.directive';
import { TrackingService } from './tracking.service';
class MockTrackingService extends TrackingService {
public eventCount = 0;
public trackEvent(eventName: string) {
this.eventCount++;
}
}
describe('TrackingDirective', () => {
let builder: TestComponentBuilder;
let mockTrackingService: MockTrackingService;
let trackingDirective: TrackingDirective;
beforeEach(() => {
mockTrackingService = new MockTrackingService();
trackingDirective = new TrackingDirective(mockTrackingService);
addProviders([
{provide: TrackingDirective, use: trackingDirective}
]);
});
beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
builder = tcb;
}));
// General button tests
it('should apply class based on color attribute', (done: () => void) => {
return builder.createAsync(TestApp).then(fixture => {
let testComponent = fixture.debugElement.componentInstance;
let buttonDebugElement = fixture.debugElement.query(By.css('button'));
buttonDebugElement.nativeElement.click();
expect(buttonDebugElement).toBeTruthy();
expect(mockTrackingService.eventCount).toBe(1);
done();
});
});
});
@Component({
selector: 'test',
template: `<button tracking="some button"></button>`,
directives: [TrackingDirective]
})
class TestApp {
}
这是我的指令代码:
import { Directive, HostListener, Input } from '@angular/core';
import { TrackingService } from './tracking.service';
@Directive({
selector: '[tracking]',
providers: [
TrackingService
]
})
export class TrackingDirective {
@Input() tracking: string;
constructor(private trackingService: TrackingService) {
}
@HostListener('click', ['$event.target'])
onClick(element) {
this.trackingService.trackEvent(this.tracking);
}
}
当我 运行 通过 ng test
进行测试时,测试失败,因为 eventCount 仍然是 0 而不是 1。
好问题!
您正在尝试测试具有自己的提供程序的指令:
@Directive({
selector: '[tracking]',
providers: [
TrackingService
]
})
在这种情况下,我们需要确保我们的 MockService
被注入到指令和测试代码中。因为要检查 eventCount
属性,所以需要注入测试代码。我建议创建 MockService
的实例并将此实例用作 TrackungService
:
的值
let mockService = new MockTrackingService();
beforeEach(() => {
addProviders([provide(TrackingService, {useValue: mockService})]);
});
MockService
的 same 实例需要用作我们指令的提供者:
builder
.overrideProviders(TrackingDirective, [provide(TrackingService, {useValue: mockService})])
.createAsync(TestApp).then(fixture => {
...
done();
});
参见builder
的方法overrideProviders
。
因此您的测试的完整代码如下所示:
/* tslint:disable:no-unused-variable */
import { inject, addProviders } from '@angular/core/testing';
import { TestComponentBuilder } from '@angular/compiler/testing';
import { Component, provide } from '@angular/core';
import { By } from '@angular/platform-browser';
import { TrackingDirective } from './tracking.directive';
import { TrackingService } from './tracking.service';
// do not extend the TrackingService. If there are other
// dependencies this would be difficult or impossible.
class MockTrackingService {
public eventCount = 0;
public trackEvent(eventName: string) {
this.eventCount++;
}
}
let mockService = new MockTrackingService();
beforeEach(() => {
addProviders([provide(TrackingService, {useValue: mockService})]);
});
describe('TrackingDirective', () => {
let builder: TestComponentBuilder;
let mockTrackingService: MockTrackingService;
beforeEach(inject([TestComponentBuilder, TrackingService],
(tcb: TestComponentBuilder, _trackingService: TrackingService) => {
builder = tcb;
// we need to cast to MockTrackingService because
// TrackingService has no eventCount property and we need it
mockTrackingService = <MockTrackingService> _trackingService;
}));
// General button tests
it('should apply class based on color attribute', (done: () => void) => {
builder
.overrideProviders(TrackingDirective, [provide(TrackingService, {useValue: mockService})])
.createAsync(TestApp).then(fixture => {
let testComponent = fixture.debugElement.componentInstance;
let buttonDebugElement = fixture.debugElement.query(By.css('button'));
buttonDebugElement.nativeElement.click();
expect(mockTrackingService.eventCount).toBe(1);
done();
});
});
});
@Component({
selector: 'test',
template: `<button tracking="some button"></button>`,
directives: [TrackingDirective]
})
class TestApp {
}
我使用 angular-cli 创建了一个简单的应用程序来说明我的问题。你可以在这里看到所有的代码: https://github.com/wholladay/tracking
每当单击包含元素时,指令都会调用服务。因此,我想模拟该服务并确保在向指令发送点击事件时调用它。
这是我的测试代码:
/* tslint:disable:no-unused-variable */
import { inject, addProviders } from '@angular/core/testing';
import { TestComponentBuilder } from '@angular/compiler/testing';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { TrackingDirective } from './tracking.directive';
import { TrackingService } from './tracking.service';
class MockTrackingService extends TrackingService {
public eventCount = 0;
public trackEvent(eventName: string) {
this.eventCount++;
}
}
describe('TrackingDirective', () => {
let builder: TestComponentBuilder;
let mockTrackingService: MockTrackingService;
let trackingDirective: TrackingDirective;
beforeEach(() => {
mockTrackingService = new MockTrackingService();
trackingDirective = new TrackingDirective(mockTrackingService);
addProviders([
{provide: TrackingDirective, use: trackingDirective}
]);
});
beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
builder = tcb;
}));
// General button tests
it('should apply class based on color attribute', (done: () => void) => {
return builder.createAsync(TestApp).then(fixture => {
let testComponent = fixture.debugElement.componentInstance;
let buttonDebugElement = fixture.debugElement.query(By.css('button'));
buttonDebugElement.nativeElement.click();
expect(buttonDebugElement).toBeTruthy();
expect(mockTrackingService.eventCount).toBe(1);
done();
});
});
});
@Component({
selector: 'test',
template: `<button tracking="some button"></button>`,
directives: [TrackingDirective]
})
class TestApp {
}
这是我的指令代码:
import { Directive, HostListener, Input } from '@angular/core';
import { TrackingService } from './tracking.service';
@Directive({
selector: '[tracking]',
providers: [
TrackingService
]
})
export class TrackingDirective {
@Input() tracking: string;
constructor(private trackingService: TrackingService) {
}
@HostListener('click', ['$event.target'])
onClick(element) {
this.trackingService.trackEvent(this.tracking);
}
}
当我 运行 通过 ng test
进行测试时,测试失败,因为 eventCount 仍然是 0 而不是 1。
好问题!
您正在尝试测试具有自己的提供程序的指令:
@Directive({
selector: '[tracking]',
providers: [
TrackingService
]
})
在这种情况下,我们需要确保我们的 MockService
被注入到指令和测试代码中。因为要检查 eventCount
属性,所以需要注入测试代码。我建议创建 MockService
的实例并将此实例用作 TrackungService
:
let mockService = new MockTrackingService();
beforeEach(() => {
addProviders([provide(TrackingService, {useValue: mockService})]);
});
MockService
的 same 实例需要用作我们指令的提供者:
builder
.overrideProviders(TrackingDirective, [provide(TrackingService, {useValue: mockService})])
.createAsync(TestApp).then(fixture => {
...
done();
});
参见builder
的方法overrideProviders
。
因此您的测试的完整代码如下所示:
/* tslint:disable:no-unused-variable */
import { inject, addProviders } from '@angular/core/testing';
import { TestComponentBuilder } from '@angular/compiler/testing';
import { Component, provide } from '@angular/core';
import { By } from '@angular/platform-browser';
import { TrackingDirective } from './tracking.directive';
import { TrackingService } from './tracking.service';
// do not extend the TrackingService. If there are other
// dependencies this would be difficult or impossible.
class MockTrackingService {
public eventCount = 0;
public trackEvent(eventName: string) {
this.eventCount++;
}
}
let mockService = new MockTrackingService();
beforeEach(() => {
addProviders([provide(TrackingService, {useValue: mockService})]);
});
describe('TrackingDirective', () => {
let builder: TestComponentBuilder;
let mockTrackingService: MockTrackingService;
beforeEach(inject([TestComponentBuilder, TrackingService],
(tcb: TestComponentBuilder, _trackingService: TrackingService) => {
builder = tcb;
// we need to cast to MockTrackingService because
// TrackingService has no eventCount property and we need it
mockTrackingService = <MockTrackingService> _trackingService;
}));
// General button tests
it('should apply class based on color attribute', (done: () => void) => {
builder
.overrideProviders(TrackingDirective, [provide(TrackingService, {useValue: mockService})])
.createAsync(TestApp).then(fixture => {
let testComponent = fixture.debugElement.componentInstance;
let buttonDebugElement = fixture.debugElement.query(By.css('button'));
buttonDebugElement.nativeElement.click();
expect(mockTrackingService.eventCount).toBe(1);
done();
});
});
});
@Component({
selector: 'test',
template: `<button tracking="some button"></button>`,
directives: [TrackingDirective]
})
class TestApp {
}