Avant l'opérateur louable, j'ai fait une aide pour modifier la méthode debounceTime, donc il utilise un 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,
        );
    });
}

Le test de l'Observable suivant était donc facile:

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

Avec les opérateurs louables, le filterUpdated $ Observable s'écrit comme ça:

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

Je ne peux plus patcher l'opérateur debounceTime! Comment puis-je passer le testScheduler à l'opérateur debounceTime?

9
Guillaume Nury 17 nov. 2017 à 19:54

5 réponses

Meilleure réponse

Vous pouvez utiliser le deuxième argument qui accepte un planificateur personnalisé.

  debounceTime(DEFAULT_DEBOUNCE_TIME, rxTestScheduler),

Tout le code

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

Puis en test

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

  //specs
  it('should update filters using debounce', inject([EffectsService], service => {
    // your test
  });
});
3
Gerard Sans 1 déc. 2017 à 23:49

J'ai eu quelques problèmes avec les réponses ci-dessus (je ne peux pas avoir plusieurs espions sur Observable.prototype, ...), pertinent pour moi était seulement de se moquer de "debounceTime" donc j'ai déplacé le debounceTime réel (par exemple filterTextDebounceTime = 200) vers une variable dans le composant et dans la spécification "beforeEach" je mets le component.filterTextDebounceTime à 0 pour que debounceTime fonctionne de manière synchrone / bloquante.

0
WerthD 25 juin 2018 à 07:28

Mettre à jour : si vous renvoyez un ensemble d'actions et que vous souhaitez toutes les vérifier, supprimez le

.pipe(throttleTime(1, myScheduler))

Et vous pouvez utiliser getTestScheduler de jasmine-marbles au lieu de créer votre propre planificateur.

import { getTestScheduler } from 'jasmine-marbles';

Un test pourrait donc ressembler à ceci:

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

J'avais du mal à tester un effet ngrx avec debounceTime. Il semble que les choses ont un peu changé maintenant. J'ai suivi la documentation ici: https://github.com/ ReactiveX / rxjs / blob / master / doc / marbre-testing.md

Voici à quoi ressemble mon test:

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

Il n'est pas nécessaire d'utiliser le planificateur dans le code réel, par exemple: no debounceTime (200, this.scheduler)

1
techguy2000 6 oct. 2018 à 06:22

S'il est difficile d'injecter ou de transmettre l'instance TestScheduler à vos opérateurs, cette solution la plus simple consiste à relier les méthodes now et schedule de l'instance AsyncScheduler à celles de { {X4}} instance.

Vous pouvez soit le faire manuellement:

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

Ou vous pouvez utiliser un stub 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 30 nov. 2017 à 07:18

Puisque .pipe() est toujours sur le prototype Observable, vous pouvez utiliser votre technique de moquerie dessus.

Opérateurs de lettres (oups, maintenant censés les appeler pipeable opérateurs) peuvent être utilisés tels quels dans le faux tube.

C'est le code que j'ai utilisé dans app.component.spec.ts d'une application CLI propre. Notez que ce n'est probablement pas la meilleure utilisation de TestScheduler, mais cela montre le principe.

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 13 janv. 2018 à 09:34
47355666