为 Angular2 应用程序单元测试模拟服务时出错

Error during mocking the service for Angular2 application unit test

我创建了一个组件,我正在尝试使用 Karma 和 Jasmine 对其进行测试。对于没有 DI 注入服务的其他组件,一切正常。但是这个抛出一个错误,没有任何消息,只有一个堆栈。

组件如下:

import {Component} from 'angular2/core';

import {Application} from './application';
import {ApplicationsService} from './applications.service';

@Component({
    selector: 'applications-selector',
    styles: [require('./applications-selector.scss')],
    template: require('./applications-selector.html'),
    providers: [ApplicationsService]
})

export class ApplicationsSelectorComponent {
    applications: Application[];
    selectedWeek: number;
    selectedApplications: Application[];
    selectedCycle: string;

    constructor(private _applicationsService: ApplicationsService) {
        this.getApplications();
    }

    getApplications() {
        this._applicationsService.getApplications().then(applications => this.applications = applications);
    }
}

下面是这个组件的单元测试:

import {
  it,
  inject,
  injectAsync,
  describe,
  beforeEachProviders,
  TestComponentBuilder
} from 'angular2/testing';
import {provide} from 'angular2/core';

import {ApplicationsSelectorComponent} from './applications-selector.component';
import {ApplicationsService} from './applications.service';

class ApplicationsServiceMock {
  getApplications() {
      return ['ABC', 'XYZ'];
  }
}

describe('ApplicationsSelectorComponent', () => {
    beforeEachProviders(() => [
        provide(ApplicationsService, { useClass: ApplicationsServiceMock }),
        ApplicationsSelectorComponent
    ]);

    it('should have empty default values', inject([ApplicationsSelectorComponent], (component) => {
        expect(component.selectedWeek).toBe(undefined);
        expect(component.selectedApplications).toBe(undefined);
        expect(component.selectedCycle).toBe(undefined);
    }));
}); 

这是我在 运行 这个测试中得到的一个错误:

ApplicationsSelectorComponent
    × should have empty default values
      PhantomJS 2.1.1 (Windows 7 0.0.0)
    _instantiateProvider@d:/git/gatekeeper/web/spec-bundle.js:11896:38 <- webpack:///angular2/src/core/di/injector.ts:770:31
    _new@d:/git/gatekeeper/web/spec-bundle.js:11885:42 <- webpack:///angular2/src/core/di/injector.ts:759:37
    getObjByKeyId@d:/git/gatekeeper/web/spec-bundle.js:11495:55 <- webpack:///angular2/src/core/di/injector.ts:356:44
    _getByKeyDefault@d:/git/gatekeeper/web/spec-bundle.js:12083:51 <- webpack:///angular2/src/core/di/injector.ts:977:44
    _getByKey@d:/git/gatekeeper/web/spec-bundle.js:12029:42 <- webpack:///angular2/src/core/di/injector.ts:914:35
    get@d:/git/gatekeeper/web/spec-bundle.js:11704:31 <- webpack:///angular2/src/core/di/injector.ts:577:26
    d:/git/gatekeeper/web/spec-bundle.js:9128:74 <- webpack:///angular2/src/testing/test_injector.ts:151:52
    map@[native code]
    apply@[native code]
    call@[native code]
    call@[native code]
    map@d:/git/gatekeeper/web/spec-bundle.js:2377:21 <- webpack:///~/es6-shim/es6-shim.js:1113:0
    execute@d:/git/gatekeeper/web/spec-bundle.js:9128:39 <- webpack:///angular2/src/testing/test_injector.ts:151:34
    execute@d:/git/gatekeeper/web/spec-bundle.js:9017:27 <- webpack:///angular2/src/testing/test_injector.ts:42:22
    d:/git/gatekeeper/web/spec-bundle.js:8393:58 <- webpack:///angular2/src/testing/testing.ts:137:49
    _instantiate@d:/git/gatekeeper/web/spec-bundle.js:12003:87 <- webpack:///angular2/src/core/di/injector.ts:883:67

inject([ApplicationsSelectorComponent]语句出现错误,我一去掉就没有错误了,但是我需要这个组件来进行测试。

什么会导致此注入错误?

您似乎正试图以与提供程序相同的方式注入组件,但该方法不起作用。

这是针对特定组件模拟提供程序的完整最小示例:

class ApplicationsService {
  getApplications() {
    return ['ABC'];
  }
}

class ApplicationsServiceMock {
  getApplications() {
    return ['ABC', 'XYZ'];
  }
}

@Component({
  selector: 'apps',
  template: '',
  providers: [ApplicationsService]
})
class ApplicationsSelectorComponent {
  constructor(private apps: ApplicationsService) {}
}

describe('App', () => {

  describe('ApplicationsSelectorComponent', () => {
    beforeEach(injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
      return tcb
        .overrideProviders(ApplicationsSelectorComponent, [provide(ApplicationsService, { useClass: ApplicationsServiceMock })])
        .createAsync(ApplicationsSelectorComponent)
        .then((componentFixture: any) => {
          this.component = componentFixture;
        });
    }));

    it('should have empty default values', () => {
      expect(this.component.componentInstance.apps.getApplications()).toEqual(['ABC', 'XYZ'])
    });

  });

});

最后,事实证明所有设置都是正确的,但我只是 return 从 ApplicationsServiceMock 中输入了错误的值。基本服务是 returning Promise 而我 returnig 只是我模拟中的一个值数组。这就是为什么当从 constructor 执行这一行 this._applicationsService.getApplications().then(applications => this.applications = applications); 时,找不到数组上的 then 方法。测试失败了。
一旦我从模拟中修复了 return 值,一切正常。
这是我测试的工作代码:

import {
    it,
    beforeEach,
    injectAsync,
    describe,
    TestComponentBuilder
} from 'angular2/testing';
import {Component, provide} from 'angular2/core';

import {Application} from './application';
import {ApplicationsService} from './applications.service';
import {ApplicationsSelectorComponent} from './applications-selector.component';


class ApplicationsServiceMock {
    getApplications() {
        return Promise.resolve([{ 'id': 1, 'name': 'TST', 'project': 'PRJ' }]);
    }
}


describe('ApplicationsSelectorComponent', () => {
  beforeEach(injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
    return tcb
      .overrideProviders(ApplicationsSelectorComponent, [provide(ApplicationsService, { useClass: ApplicationsServiceMock })])
      .createAsync(ApplicationsSelectorComponent)
      .then((componentFixture: any) => {
        this.component = componentFixture;
      });
  }));

  it('should have empty default values', () => {
      expect(this.component.componentInstance._applicationsService.getApplications().then(apps => { apps.toEqual([{ 'id': 1, 'name': 'TST', 'project': 'PRJ' }]) }));
  });

});