サービスで使用されるコンポーネントの単体テストを作成しようとしています。コンポーネントとサービスは正常に動作します。
成分:
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}]
。
質問:サービスをモックする方法は?
これが原因です
@Component({
providers: [PonyService] <======
})
これにより、サービスがコンポーネントにスコープされるようになります。つまり、Angularはコンポーネントごとにサービスを作成し、モジュールレベルで設定されたグローバルプロバイダーに優先します。テストベッドで構成する模擬プロバイダー。
これを回避するには、AngularはTestBed.overrideComponent
メソッド。@Component.providers
および@Component.template
。
TestBed.configureTestingModule({
declarations: [PoniesComponent, PonyComponent]
})
.overrideComponent(PoniesComponent, {
set: {
providers: [
{provide: PonyService, useClass: MockPonyService}
]
}
});
別の有効なアプローチは、トークンを使用し、基本クラスまたは具象クラスの代わりにインターフェイスに依存することです。私のような恐竜はこれが大好きです( [〜#〜] 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の動作方法により、インターフェイスよりもベースクラスを好むようです。