Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mock delay() RxJS with Jest

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.

like image 379
Dujard Avatar asked Apr 08 '19 08:04

Dujard


Video Answer


3 Answers

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
  `);
});
like image 63
Ivan Avatar answered Oct 03 '22 22:10

Ivan


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();
  });
like image 26
ylliac Avatar answered Oct 03 '22 22:10

ylliac


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!
  });
});
like image 23
Brian Adams Avatar answered Oct 03 '22 22:10

Brian Adams