web-dev-qa-db-ja.com

単体テスト中にangle2のカスタムサービスを模擬する

サービスで使用されるコンポーネントの単体テストを作成しようとしています。コンポーネントとサービスは正常に動作します。

成分:

import {Component} from '@angular/core';
import {PonyService} from '../../services';
import {Pony} from "../../models/pony.model";
@Component({
  selector: 'el-ponies',
  templateUrl: 'ponies.component.html',
  providers: [PonyService]
})
export class PoniesComponent {
  ponies: Array<Pony>;
  constructor(private ponyService: PonyService) {
    this.ponies = this.ponyService.getPonies(2);
  }
  refreshPonies() {
    this.ponies = this.ponyService.getPonies(3);
  }
}

サービス:

import {Injectable} from "@angular/core";
import {Http} from "@angular/http";
import {Pony} from "../../models/pony.model";
@Injectable()
export class PonyService {
  constructor(private http: Http) {}
  getPonies(count: number): Array<Pony> {
    let toReturn: Array<Pony> = [];
    this.http.get('http://localhost:8080/js-backend/ponies')
    .subscribe(response => {
      response.json().forEach((tmp: Pony)=> { toReturn.Push(tmp); });
      if (count && count % 2 === 0) { toReturn.splice(0, count); } 
      else { toReturn.splice(count); }
    });
    return toReturn;
  }}

コンポーネント単体テスト:

import {TestBed} from "@angular/core/testing";
import {PoniesComponent} from "./ponies.component";
import {PonyComponent} from "../pony/pony.component";
import {PonyService} from "../../services";
import {Pony} from "../../models/pony.model";
describe('Ponies component test', () => {
  let poniesComponent: PoniesComponent;
  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [PoniesComponent, PonyComponent],
      providers: [{provide: PonyService, useClass: MockPonyService}]
    });
    poniesComponent = TestBed.createComponent(PoniesComponent).componentInstance;
  });
  it('should instantiate component', () => {
    expect(poniesComponent instanceof PoniesComponent).toBe(true, 'should create PoniesComponent');
  });
});

class MockPonyService {
  getPonies(count: number): Array<Pony> {
    let toReturn: Array<Pony> = [];
    if (count === 2) {
      toReturn.Push(new Pony('Rainbow Dash', 'green'));
      toReturn.Push(new Pony('Pinkie Pie', 'orange'));
    }
    if (count === 3) {
      toReturn.Push(new Pony('Fluttershy', 'blue'));
      toReturn.Push(new Pony('Rarity', 'purple'));
      toReturn.Push(new Pony('Applejack', 'yellow'));
    }
    return toReturn;
  };
}

Package.jsonの一部:

{
  ...
  "dependencies": {
    "@angular/core": "2.0.0",
    "@angular/http": "2.0.0",
    ...
  },
  "devDependencies": {
    "jasmine-core": "2.4.1",
    "karma": "1.2.0",
    "karma-jasmine": "1.0.2",
    "karma-phantomjs-launcher": "1.0.2",
    "phantomjs-prebuilt": "2.1.7",
    ...
  }
}

「karma start」を実行すると、このエラーが発生します

エラー:./PoniesComponentクラスPoniesComponent_Hostのエラー-インラインテンプレート:0:0原因:Httpのプロバイダーがありません! config/karma-test-shim.js

この行にもかかわらず、カルマはPonyServiceとしてモックする代わりにMockPonyServiceを使用しているように見えます:providers: [{provide: PonyService, useClass: MockPonyService}]

質問:サービスをモックする方法は?

16
Evgeniy

これが原因です

@Component({
  providers: [PonyService]  <======
})

これにより、サービスがコンポーネントにスコープされるようになります。つまり、Angularはコンポーネントごとにサービスを作成し、モジュールレベルで設定されたグローバルプロバイダーに優先します。テストベッドで構成する模擬プロバイダー。

これを回避するには、AngularはTestBed.overrideComponentメソッド。@Component.providersおよび@Component.template

TestBed.configureTestingModule({
  declarations: [PoniesComponent, PonyComponent]
})
.overrideComponent(PoniesComponent, {
  set: {
    providers: [
      {provide: PonyService, useClass: MockPonyService}
    ]
  }
});
22
Paul Samsotha

別の有効なアプローチは、トークンを使用し、基本クラスまたは具象クラスの代わりにインターフェイスに依存することです。私のような恐竜はこれが大好きです( [〜#〜] dip [〜#〜][〜#〜] di [〜#〜] 、および他のSOLID Blablahs)。そして、コンポーネントに独自のコンポーネントを提供する代わりに、コンポーネントに依存関係を注入できるようにします。

コンポーネントにはプロバイダーがなく、angularの間にコンストラクターでオブジェクトをインターフェイスとして受け取ります。 マジック 依存性注入。コンストラクタで使用される@injectを参照し、クラスではなくテキストとしてプロバイダーの 'provide'値を参照してください。

したがって、コンポーネントは次のように変更されます。

constructor(@Inject('PonyServiceInterface') private ponyService: IPonyService) {
   this.ponies = this.ponyService.getPonies(2); }

@Componentパーツで、プロバイダーを削除し、「app.component.ts」などの親コンポーネントに追加します。そこでトークンを追加します:

providers: [{provide: 'PonyServiceInterface', useClass: PonyService}]

単体テストコンポーネント(app.component.tsに類似)には次のものがあります:プロバイダー:[{provide: 'PonyServiceInterface'、useClass:MockPonyService}]

したがって、コンポーネントはサービスが何をするかを気にせず、親コンポーネント(app.component.tsまたはユニットテストコンポーネント)を介して注入されたインターフェイスを使用するだけです。

参考:@injectアプローチはあまり広く使用されておらず、ある時点で、angularフェローは、基礎となるjavascriptの動作方法により、インターフェイスよりもベースクラスを好むようです。

1