15

Is there easy way to mock delay() method of RxJS in an observable with a fake time for example ?

I have this method :

register(user) {
  return this._checkLog(user).delay(500).flatMap( ... )
}

when i remove delay() method, my tests from _register() all success.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Dujard
  • 271
  • 2
  • 7
  • 23

3 Answers3

20

RxJS v6

For RxJS v6 code like this:

code.js

import { of } from 'rxjs';
import { delay } from 'rxjs/operators';

export const example = of('hello').pipe(
  delay(1000)
);

...you can use sinon fake timers like this:

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

(Note that at time of writing Jest timer mocks don't work, not sure why)


...or you can mock delay to do nothing like this:

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

For RxJS v5 code like this:

code.js

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/delay';

export const example = Observable.of('hello').delay(1000);

...you can mock delay to do nothing like this:

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!
  });
});
Brian Adams
  • 43,011
  • 9
  • 113
  • 111
  • 1
    The approach with jest.mock is just so simple and clean. I applaud you! – Riscie Sep 16 '22 at 15:46
  • 1
    Works with RxJs 7 : ` const operators = jest.requireActual('rxjs/operators'); jest.spyOn(operators, 'delay').mockImplementation(() => (s: any) => s); ` – isy Jan 06 '23 at 14:39
4

to complete brian-live-outdoor solution for RxJS 6, you can also mock the real behavior of delay() using delayWhen and timer which work with 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;
});

and you can put this mock next to the node_modules folder :

.
├── __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;

An example of test which didn't worked before and work with the mock:

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();
  });
ylliac
  • 173
  • 3
  • 9
1

We are using Schedulers from Rxjs.

Class looking something like this:

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

Then, in the spec file providing a mock in the TestModuleMetadata:

{
    declarations: [YourComponent],
    imports: [],
    providers: [
        { provide: Scheduler, useValue: new VirtualTimeScheduler() },
    ],
};

Now all you need to do is to assign the scheduler in a beforeEach block and flush it whenever you want the delay to be "skipped":

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

This also works with other time-dependend operators like bufferTime. Which scheduler you want to use or need to use in the component should be up to your usecase, in the best case look up the documentation and figure out what fits you best.

Marv
  • 748
  • 11
  • 27