54

サービス関数をサブスクライブするコンポーネントがあるとします。

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.
    }
);

これらのような匿名関数をテストするためのベスト プラクティスは何ですか? テスト カバレッジを確保するための最低限の要件は何ですか?

4

3 に答える 3

21

モック サービスをテストしようとしている、表示しているコードの目的が正確にわかりません。カバレッジの問題はコンポーネントにあり、エラー コールバックが呼び出されていません (エラーが発生した場合にのみ呼び出されます)。

ほとんどの監視可能なサービスに対して私が通常行うことは、メソッドが自分自身を返すだけのモックを作成することです。モック サービスには、、、およびコールバックsubscribeを受け入れるメソッドがあります。モックのユーザーは、関数が呼び出されるようにエラーを追加するか、メソッドが呼び出されるようにデータを追加するように構成できます。これについて私が最も気に入っているのは、すべて同期していることです。nexterrorcompleteerrornext

以下は、私が通常使用しているもののようなものです。他のモックが拡張するための単なる抽象クラスです。オブザーバブルが提供する基本的な機能を提供します。拡張モック サービスは、必要なメソッドを追加するだけで、メソッドでそれ自体を返す必要があります。

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();
})
于 2016-10-10T14:41:56.887 に答える