Есть ли простой способ смоделировать delay()
метод RxJS в наблюдаемом объекте, например, с поддельным временем?
У меня есть этот метод:
register(user) {
return this._checkLog(user).delay(500).flatMap( ... )
}
когда я удаляю метод delay()
, мои тесты из _register() проходят успешно.
Для кода RxJS v6 вот так:
код.js
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
export const example = of('hello').pipe(
delay(1000)
);
... вы можете использовать sinon
поддельные таймеры следующим образом:
import * as sinon from 'sinon';
import { example } from './code';
describe('delay', () => {
let clock;
beforeEach(() => { clock = sinon.useFakeTimers(); });
afterEach(() => { clock.restore(); });
it('should delay one second', () => {
const spy = jest.fn();
example.subscribe(spy);
expect(spy).not.toHaveBeenCalled(); // Success!
clock.tick(1000);
expect(spy).toHaveBeenCalledWith('hello'); // Success!
});
});
(Обратите внимание, что на момент написания статьи Jest
таймер издевается не работали, не знаю почему)
... или вы можете издеваться над delay
, чтобы ничего не делать:
import { delay } from 'rxjs/operators';
import { example } from './code';
jest.mock('rxjs/operators', () => {
const operators = jest.requireActual('rxjs/operators');
operators.delay = jest.fn(() => (s) => s); // <= mock delay
return operators;
});
describe('delay', () => {
it('should delay one second', () => {
const spy = jest.fn();
example.subscribe(spy);
expect(delay).toHaveBeenCalledWith(1000); // Success!
expect(spy).toHaveBeenCalledWith('hello'); // Success!
});
});
Для кода RxJS v5 следующим образом:
код.js
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/delay';
export const example = Observable.of('hello').delay(1000);
... вы можете издеваться над delay
, чтобы ничего не делать:
import { Observable } from 'rxjs/Observable';
import { example } from './code';
jest.mock('rxjs/add/operator/delay', () => {
const Observable = require('rxjs/Observable').Observable;
Observable.prototype.delay = jest.fn(function () { return this; }); // <= mock delay
});
describe('delay', () => {
it('should delay one second', () => {
const spy = jest.fn();
example.subscribe(spy);
expect(Observable.prototype.delay).toHaveBeenCalledWith(1000); // Success!
expect(spy).toHaveBeenCalledWith('hello'); // Success!
});
});
Мы используем Scheduler
s от Rxjs.
Класс выглядит примерно так:
import { Observable, Scheduler, Subject, asapScheduler } from 'rxjs';
// ...
constructor(
@Optional() private readonly _scheduler: Scheduler
) {
if (isNullOrUndefined(_scheduler)) {
this._scheduler = asapScheduler;
}
}
// ...
this._someObservable.pipe(delay(1, this._scheduler));
Затем в файле спецификации, предоставляющем макет в TestModuleMetadata
:
{
declarations: [YourComponent],
imports: [],
providers: [
{ provide: Scheduler, useValue: new VirtualTimeScheduler() },
],
};
Теперь все, что вам нужно сделать, это назначить планировщик в блоке beforeEach
и очищать его всякий раз, когда вы хотите, чтобы задержка была «пропущена»:
let schedulerMock = Testbed.get(Scheduler);
// ...
it('should emit true', () => {
let result: boolean = null;
comp.someObservable.subscribe(next => (result = next));
schedulerMock.flush();
expect(result).toBe(true);
});
Это также работает с другими операторами, зависящими от времени, такими как bufferTime
.
Какой планировщик вы хотите использовать или должны использовать в компоненте, должен зависеть от вашего варианта использования, в лучшем случае посмотрите документацию и выясните, что подходит вам лучше всего.
чтобы завершить решение brian-live-outdoor для RxJS 6, вы также можете смоделировать реальное поведение delay(), используя delayWhen и timer, которые работают с jest:
jest.mock("rxjs/operators", () => {
const operators = jest.requireActual("rxjs/operators");
const observables = jest.requireActual("rxjs");
operators.delay = jest.fn(delay => s =>
s.pipe(operators.delayWhen(() => observables.timer(delay)))
);
return operators;
});
и вы можете поместить этот макет рядом с папкой node_modules:
.
├── __mocks__
│ └── rxjs
│ └── operators.js
└── node_modules
// operators.js
const operators = require("rxjs/operators");
const observables = require("rxjs");
operators.delay = jest.fn(delay => s =>
s.pipe(operators.delayWhen(() => observables.timer(delay)))
);
module.exports = operators;
Пример теста, который раньше не работал и работает с макетом:
it("some test with delay()", (done: DoneFn) => {
let check = false;
jest.useFakeTimers();
of(true)
.pipe(delay(1000))
.subscribe(() => (check = true));
setTimeout(() => {
expect(check).toBe(true);
done();
}, 2000);
jest.runTimersToTime(999);
expect(check).toBe(false);
jest.runAllTimers();
});
это прекрасно сработало для меня. Благодарю.
Начиная с версии 6.2.1, RxJS поддерживает поддельное время Jest, поэтому вы можете просто написать
jest.useFakeTimers('modern');
test('...', () => {
// ...code that subscribes to your observable.
jest.runAllTimers();
// ...code that makes assertions.
});
Кроме того, я опубликовал библиотека, который упрощает написание подобных тестов, вот пример (log
добавляет ведение журнала к наблюдаемому, getMessages
извлекает зарегистрированные сообщения):
import { getMessages, log } from '1log';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
test('delay', () => {
of(42).pipe(delay(500), log).subscribe();
jest.runAllTimers();
expect(getMessages()).toMatchInlineSnapshot(`
[create 1] +0ms [Observable]
[create 1] [subscribe 1] +0ms [Subscriber]
[create 1] [subscribe 1] [next] +500ms 42
[create 1] [subscribe 1] [complete] +0ms
· [create 1] [subscribe 1] [unsubscribe] +0ms
`);
});