サービス関数をサブスクライブするコンポーネントがあるとしましょう:
export class Component {
...
ngOnInit() {
this.service.doStuff().subscribe(
(data: IData) => {
doThings(data);
},
(error: Error) => console.error(error)
);
};
};
サブスクライブ呼び出しは、パラメーターとして2つの匿名関数を取ります。データ関数のワーキングユニットテストを設定できましたが、Karmaはエラー1のカバレッジを受け入れません。
Console.error関数をスパイして、エラーをスローし、スパイが呼び出されることを期待してみましたが、それではうまくいきません。
私の単体テスト:
spyOn(console,'error').and.callThrough();
serviceStub = {
doStuff: jasmine.createSpy('doStuff').and.returnValue(Observable.of(data)),
};
serviceStub.doStuff.and.returnValue(Observable.throw(
'error!'
));
serviceStub.doStuff().subscribe(
(res) => {
*working test, can access res*
},
(error) => {
console.error(error);
console.log(error); //Prints 'error!' so throw works.
expect(console.error).toHaveBeenCalledWith('error!'); //Is true but won't be accepted for coverage.
}
);
これらのような匿名関数をテストするためのベストプラクティスは何ですか?テストカバレッジを確保するために最低限必要なものは何ですか?
単にObservable.throw({status: 404})
のようなObservable throwエラーオブジェクトをモックし、observableのエラーブロックをテストできます。
const xService = fixture.debugElement.injector.get(SomeService);
const mockCall = spyOn(xService, 'method')
.and.returnValue(Observable.throw({status: 404}));
2019年更新:
コメントを読むのが面倒な人もいるので、ここにこれを書いてみましょう。Rxjsでエラーを使用するのがベストプラクティスです
import { throwError } from 'rxjs'
const xService = fixture.debugElement.injector.get(SomeService);
const mockCall = spyOn(xService,'method').and.returnValue(throwError({status: 404}));
表示しているコードの目的が正確にわかりません。これは、模擬サービスをテストしようとしています。カバレッジの問題は、コンポーネントとエラーコールバックが呼び出されていないことです(エラーが発生した場合にのみ呼び出されます)。
私が観察可能なサービスのほとんどで通常行うことは、メソッドがそれ自体を返すだけのモックを作成することです。モックサービスには、subscribe
、next
、およびerror
コールバックを受け入れるcomplete
メソッドがあります。モックのユーザーは、error
関数が呼び出されるようにエラーを追加するようにモックを構成するか、next
メソッドが呼び出されるようにデータを追加します。私がこれについて一番気に入っているのは、すべて同期的だということです。
以下は、私が通常使用するもののようなものです。他のモックを拡張するための単なる抽象クラスです。オブザーバブルが提供する基本的な機能を提供します。拡張するモックサービスは、必要なメソッドを追加するだけで、メソッドで自身を返す必要があります。
import { Subscription } from 'rxjs/Subscription';
export abstract class AbstractMockObservableService {
protected _subscription: Subscription;
protected _fakeContent: any;
protected _fakeError: any;
set error(err) {
this._fakeError = err;
}
set content(data) {
this._fakeContent = data;
}
get subscription(): Subscription {
return this._subscription;
}
subscribe(next: Function, error?: Function, complete?: Function): Subscription {
this._subscription = new Subscription();
spyOn(this._subscription, 'unsubscribe');
if (next && this._fakeContent && !this._fakeError) {
next(this._fakeContent);
}
if (error && this._fakeError) {
error(this._fakeError);
}
if (complete) {
complete();
}
return this._subscription;
}
}
テストでは、次のようなことをするだけです。
class MockService extends AbstractMockObservableService {
doStuff() {
return this;
}
}
let mockService;
beforeEach(() => {
mockService = new MockService();
TestBed.configureTestingModule({
providers: [{provide: SomeService, useValue: mockService }],
declarations: [ TestComponent ]
});
});
it('should call service success', () => {
mockService.content = 'some content';
let fixture = TestBed.createComponent(TestComponent);
// test component for success case
});
it('should call service error', () => {
mockService.error = 'Some error';
let fixture = TestBed.createComponent(TestComponent);
// test component for error case
// this should handle your coverage problem
});
// this assumes you have unsubscribed from the subscription in your
// component, which you should always do in the ngOnDestroy of the component
it('should unsubscribe when component destroyed', () => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
fixture.destroy();
expect(mockService.subscription.unsubscribe).toHaveBeenCalled();
})