Mock delay() RxJS with Jest

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

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.


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

Since version 6.2.1, RxJS supports Jest's fake time, so you can simply write

jest.useFakeTimers('modern');

test('...', () => {
  // ...code that subscribes to your observable.

  jest.runAllTimers();

  // ...code that makes assertions.
});

Also, I've published a library that makes writing tests like these easier, here's an example (log adds logging to an observable, getMessages retrieves logged messages):

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