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?
5 réponses
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
});
});
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.
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)
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();
});
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();
});
});
Questions connexes
De nouvelles questions
typescript
TypeScript est un sur-ensemble typé de JavaScript qui se compile en JavaScript brut. Il ajoute des types, classes, interfaces et modules facultatifs à JavaScript. Cette balise est destinée aux questions spécifiques à TypeScript. Il n'est pas utilisé pour les questions JavaScript générales.