你如何测试调用服务的Angular 2指令?

时间:2016-07-22 05:48:33

标签: angular angular2-directives angular2-services angular2-testing

我使用angular-cli创建了一个简单的应用来说明我的问题。您可以在此处查看所有代码: https://github.com/wholladay/tracking

只要单击包含元素,该指令就会调用服务。因此,我想模拟服务并确保在向指令发送click事件时调用它。

这是我的测试代码:

/* 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。

1 个答案:

答案 0 :(得分:3)

很棒的问题!

您正在尝试测试具有自己的提供程序的指令:

@Directive({
  selector: '[tracking]',
  providers: [
    TrackingService
  ]
})

在这种情况下,我们需要确保我们的MockService被注入到指令和测试代码中。需要注入测试代码,因为您要检查eventCount属性。我建议您创建MockService的实例,并将此实例用作TrackungService的值:

let mockService = new MockTrackingService();

beforeEach(() => {  
  addProviders([provide(TrackingService, {useValue: mockService})]);
});

MockService相同实例需要用作我们指令的提供者:

builder
      .overrideProviders(TrackingDirective, [provide(TrackingService, {useValue: mockService})])
      .createAsync(TestApp).then(fixture => {

...

      done();
    });

请参阅overrideProviders的方法builder

因此,测试的完整代码如下所示:

/* 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 {
}