RxJS Observables は、非同期コードを作成するための非常に強力でエレガントな方法ですが、テストが複雑になる可能性があります。 大理石のテストを使用すると、テストがはるかに簡単になります。

この投稿では、大理石のテストと、それを使用してColorMixerをテストする方法の例について説明します。 ColorMixer の例とテストはTypescriptで記述されていますが、RxJSと大理石のテストはバニラJavascriptでも使用できます。

この投稿は、 RxJS Observablesと演算子の基本的な知識があることを前提としています。

大理石の図

大理石の図は、オブザーバブルを視覚的に表現する方法です。 ビー玉は放出される値を表し、時間の経過は左から右に表され、垂直線は Observable の完了を表し、Xはエラーを表します。

これらの基本的な部分だけで、任意のObservableを表すことができます。 これらは、オペレーターがオブザーバブルをどのように変換するかを示すために最も一般的に使用されます。 これは、 debounceTime演算子 ColorMixer で使用)のRxJSドキュメントの例です。

debounceTime

reactx.ioの画像提供。 大理石の図について詳しくは、こちらをご覧ください。


大理石のテストでは、テストで大理石の図を使用しています。 大理石のテストには複数のライブラリがありますが、jasminerxjs-を使用してColorMixerをテストするため、例ではjasmine-marblesを使用します。大理石は、テストフレームワークに依存しないもう1つの優れた実装です。

大理石のテストについて知っておくべきことはすべてここにありますが、基本は次のとおりです。

  • RxJS TestScheduler は、時間の経過と、テストで作成されたObservablesから値が出力されるタイミングを制御します。
  • Observables は、 cold(marbles、values?、errors?)(テストの開始時にサブスクリプションが開始されます)または hot(marbles、values?、errors?)[で作成されます。 X163X](テストの開始時にすでに「実行中」)メソッド。
  • は、10フレームの時間の経過を表します。
  • | は、Observableの完了を表します。
  • ^ は、 Observable のサブスクリプションポイントを表します(ホット Observables でのみ有効)。
  • はエラーを表します。 エラーの値は、errors引数に指定できます。
  • その他の文字は、放出された値を表します。 実際の値は、 values 引数で表すことができます。ここで、文字はキーです。
  • 最後に、ObservablesexpectObservableメソッドと比較できます。

ColorMixer をテストするには、最初に大理石のテストライブラリをインストールする必要があります。

npm install jasmine-marbles --save-dev

ColorMixerのテスト

ColorMixer には、 mix という1つの静的メソッドがあり、色がミキサーに入るかどうかのObservablesを取得します。 このメソッドを実行すると、ミキサーが出力している色のObservableが返されます。 また、一定期間色を混ぜ合わせて、出てくる色がうまく混ざり合うようにします。

color.enum.ts
export enum Color {
  NONE,
  RED,
  ORANGE,
  YELLOW,
  GREEN,
  BLUE,
  PURPLE,
  BLACK
}
color-mixer.ts
import 'rxjs/add/observable/combineLatest';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/startWith';
import { Observable } from 'rxjs/Observable';
import { IScheduler } from 'rxjs/Scheduler';
import { async } from 'rxjs/scheduler/async';
import { Color } from './color.enum';

export class ColorMixer {
  static mix(r: Observable<boolean>,
             y: Observable<boolean>,
             b: Observable<boolean>,
             // Allow configuration during testing
             mixingTime = 1000,
             // Allow the use of the TestScheduler during testing
             scheduler: IScheduler = async): Observable<Color> {
return Observable.combineLatest(
  // Every color starts off
  r.startWith(false),
  y.startWith(false),
  b.startWith(false),

  // Mix the colors
  (redOn, yellowOn, blueOn) => {

    if (!redOn && !yellowOn && !blueOn) {
      return Color.NONE;
    } else if (redOn && !yellowOn && !blueOn) {
      return Color.RED;
    } else if (redOn && yellowOn && !blueOn) {
      return Color.ORANGE;
    } else if (!redOn && yellowOn && !blueOn) {
      return Color.YELLOW;
    } else if (!redOn && yellowOn && blueOn) {
      return Color.GREEN;
    } else if (!redOn && !yellowOn && blueOn) {
      return Color.BLUE;
    } else if (redOn && !yellowOn && blueOn) {
      return Color.PURPLE;
    } else {
      return Color.BLACK;
    }
})
  .debounceTime(mixingTime, scheduler)
  .startWith(Color.NONE)
  .distinctUntilChanged();  }


ColorMixer は、 debounceTime 演算子のため、Schedulerを使用します。 マーブルテストを適切に行うには、TestSchedulerを使用して仮想クロックとして機能するように指示する必要があります。 また、 mixedTime を、ミリ秒ではなく必要なフレーム数に変更する必要があります。 ColorMixerを適切にテストできるようになりました。

color-mixer.spec.ts
import { ColorMixer } from './color-mixer';
import { cold, getTestScheduler } from 'jasmine-marbles';
import { Color } from './color.enum';
import { Observable } from 'rxjs/Observable';

describe('ColorMixer', () => {
  describe('mix', () => {
it('should mix colors', () => {
  const r = cold('--o--x--|', onOffMarbles());
  const y = cold('--------|', onOffMarbles());
  const b = cold('--o-----|', onOffMarbles());      // Start mixing red and blue @ frame 20.
      // Purple is made @ frame 40 (20 frame mixing time).
      // Remove red @ frame 50 to make blue @ frame 70.
      const c = cold('x---p--b|', colorMarbles());
  expect(mix(r, y, b)).toBeObservable(c);
});  });
});
// Change the mixing time to 20 frames and use the TestScheduler
function mix(r: Observable<boolean>,
             y: Observable<boolean>,
             b: Observable<boolean>) {
  return ColorMixer.mix(r, y, b, 20, getTestScheduler());
}
// Marble values representing on/off
function onOffMarbles() {
  return {
    o: true,
    x: false
  }
}

テストに合格すると、 expected(…).toBeObservable(…)は他のアサーションと同じように機能します。 アサーションが失敗すると、Observableの各フレームで何が起こったかを説明する詳細なログが出力されます。 予想されるObservableの最後にColor.BLUE大理石を追加するのを忘れた場合、次のようになります。

Expected
       {"frame":0,"notification":{"kind":"N","value":0,"hasValue":true}}
       {"frame":40,"notification":{"kind":"N","value":6,"hasValue":true}}
       {"frame":70,"notification":{"kind":"N","value":5,"hasValue":true}}
       {"frame":80,"notification":{"kind":"C","hasValue":false}}

to deep equal
       {"frame":0,"notification":{"kind":"N","value":0,"hasValue":true}}
       {"frame":40,"notification":{"kind":"N","value":6,"hasValue":true}}
       {"frame":80,"notification":{"kind":"C","hasValue":false}}

値は、Color列挙値に対応します。 Color.BLUE がフレーム70で放出されたのは明らかですが、これをアサーションに追加するのを忘れていました。


大理石のテストでは、Observablesを視覚的に👀テストする方法が可能です。 テストと読み取りが簡単になります。

observable $ +(jasmine-marbles || rxjs-marbles)===😍