web-dev-qa-db-ja.com

RxJS5.5でのlettableオペレーターのテストとモック

Lettable演算子の前に、debounceTimeメソッドを変更するヘルパーを実行したので、TestSchedulerを使用します。

export function mockDebounceTime(
    scheduler: TestScheduler,
    overrideTime: number,
): void {
    const originalDebounce = Observable.prototype.debounceTime;

    spyOn(Observable.prototype, 'debounceTime').and.callFake(function(
        time: number,
    ): void {
        return originalDebounce.call(
            this,
            overrideTime,
            scheduler,
        );
    });
}

したがって、次のObservableのテストは簡単でした。

@Effect()
public filterUpdated$ = this.actions$
    .ofType(UPDATE_FILTERS)
    .debounceTime(DEFAULT_DEBOUNCE_TIME)
    .mergeMap(action => [...])

Lettable演算子を使用すると、filterUpdated $ Observableは次のように記述されます。

@Effect()
public filterUpdated$ = this.actions$
    .ofType(UPDATE_FILTERS)
    .pipe(
        debounceTime(DEFAULT_DEBOUNCE_TIME),
        mergeMap(action => [...])
    );

DebounceTime演算子にパッチを適用できなくなりました! testSchedulerをdebounceTimeオペレーターに渡すにはどうすればよいですか?

9
Guillaume Nury

カスタムスケジューラを受け入れる2番目の引数を使用できます。

  debounceTime(DEFAULT_DEBOUNCE_TIME, rxTestScheduler),

すべてのコード

import { Scheduler } from 'rxjs/scheduler/Scheduler';
import { asap } from 'rxjs/scheduler/asap';

@Injectable()
export class EffectsService {
  constructor(private scheduler: Scheduler = asap) { }

  @Effect()
  public filterUpdated$ = this.actions$
    .ofType(UPDATE_FILTERS)
    .pipe(
        debounceTime(DEFAULT_DEBOUNCE_TIME, this.scheduler),
        mergeMap(action => [...])
    );
}

その後、テストで

describe('Service: EffectsService', () => {
  //setup
  beforeEach(() => TestBed.configureTestingModule({
    EffectsService, 
    { provide: Scheduler, useValue: rxTestScheduler} ]
  }));

  //specs
  it('should update filters using debounce', inject([EffectsService], service => {
    // your test
  });
});
2
Gerard Sans

.pipe()はまだObservableプロトタイプ上にあるため、モック手法を使用できます。

Lettable演算子(おっと、今ではそれらを呼び出すことになっています pipeable演算子 )は、モックパイプ内でそのまま使用できます。

これは、クリーンなCLIアプリケーションのapp.component.spec.tsで使用したコードです。 TestSchedulerの最善の使用法ではないかもしれませんが、原則を示していることに注意してください。

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { Observable } from 'rxjs/Observable';
import { debounceTime, take, tap } from 'rxjs/operators';
import { TestScheduler } from 'rxjs/Rx';

export function mockPipe(...mockArgs) {
  const originalPipe = Observable.prototype.pipe;
  spyOn(Observable.prototype, 'pipe').and.callFake(function(...actualArgs) {
    const args = [...actualArgs];
    mockArgs.forEach((mockArg, index) => {
      if(mockArg) {
        args[index] = mockArg;
      }
    });
    return originalPipe.call(this, ...args);
  });
}

describe('AppComponent', () => {
  it('should test lettable operators', () => {
    const scheduler = new TestScheduler(null);

    // Leave first tap() as-is but mock debounceTime()
    mockPipe(null, debounceTime(300, scheduler));   

    const sut = Observable.timer(0, 300).take(10)
      .pipe(
        tap(x => console.log('before ', x)),
        debounceTime(300),
        tap(x => console.log('after ', x)),
        take(4),
      );
    sut.subscribe((data) => console.log(data));
    scheduler.flush();
  });
});
4
Richard Matsen

TestSchedulerインスタンスを演算子に挿入または渡すことが難しい場合、この最も簡単な解決策は、nowインスタンスのscheduleメソッドとAsyncSchedulerメソッドをに再バインドすることです。 TestSchedulerインスタンスのもの。

これは手動で行うことができます。

import { async } from "rxjs/Scheduler/async";

it("should rebind to the test scheduler", () => {

  const testScheduler = new TestScheduler();
  async.now = () => testScheduler.now();
  async.schedule = (work, delay, state) => testScheduler.schedule(work, delay, state);

  // test something

  delete async.now;
  delete async.schedule;
});

または、sinonスタブを使用できます。

import { async } from "rxjs/Scheduler/async";
import * as sinon from "sinon";

it("should rebind to the test scheduler", () => {

  const testScheduler = new TestScheduler();
  const stubNow = sinon.stub(async, "now").callsFake(
      () => testScheduler.now()
  );
  const stubSchedule = sinon.stub(async, "schedule").callsFake(
      (work, delay, state) => testScheduler.schedule(work, delay, state)
  );

  // test something

  stubNow.restore();
  stubSchedule.restore();
});
1
cartant

更新:アクションの配列を返し、それらすべてを検証する場合は、

.pipe(throttleTime(1, myScheduler))

また、独自のスケジューラを作成する代わりに、jasmine-marblesからgetTestSchedulerを使用できます。

import { getTestScheduler } from 'jasmine-marbles';

したがって、テストは次のようになります。

  it('should pass', () => {
    getTestScheduler().run((helpers) => {
      const action = new fromAppActions.LoadApps();
      const completion1 = new fromAppActions.FetchData();
      const completion2 = new fromAppActions.ShowWelcome();
      actions$ = helpers.hot('-a', { a: action });
      helpers
        .expectObservable(effects.load$)
        .toBe('300ms -(bc)', { b: completion1, c: completion2 });
    });
  });

DebounceTimeでngrx効果をテストするのに苦労していました。今は少し変わったようです。私はここのドキュメントに従いました: https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md

これが私のテストの様子です:

    describe('someEffect$', () => {
      const myScheduler = new TestScheduler((a, b) => expect(a).toEqual(b));
      it('should test', () => {
        myScheduler.run((helpers) => {
          const action = new fromActions.SomeAction();
          const completion = new fromActions.SomeCompleteAction(someData);
          actions$.stream = helpers.hot('-a', { a: action });

          helpers
            .expectObservable(effects.someEffect$.pipe(throttleTime(1, myScheduler)))
            .toBe('200ms -(b)', { b: completion });
        });
      });
    });

実際のコードでスケジューラーを使用する必要はありません。例:no debounceTime(200、this.scheduler)

1
techguy2000

上記の回答にいくつか問題がありました(Observable.prototypeに複数のスパイを含めることはできません...)、私に関連するのは「debounceTime」をモックするだけだったので、実際のdebounceTime(たとえばfilterTextDebounceTime = 200)をの変数に移動しましたコンポーネントと仕様の「beforeEach」で、component.filterTextDebounceTimeを0に設定しているので、debounceTimeは同期/ブロッキングで機能しています。

0
WerthD