首页 文章

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

提问于
浏览
2

我使用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 回答

  • 3

    好问题!

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

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

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

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

    MockServicesame 实例需要用作我们指令的提供者:

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

相关问题