Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jest.spyOn method that return a Promise

I'm trying to test an application but jest.spyOn is make me crazy.

I'm working on node v8.9.4 and jest v22.2.2

I have extracted this code:

// my-module.js
class MySingletonModule {
  constructor() {
    this.foo = 'bar';
  }
  myPromise() {
    return new Promise((resolve, reject) => resolve(this.foo));
  }
}

module.exports = new MySingletonModule();

// promise.test.js
const singleton = require('./my-module');
describe('Module test-suite', () => {
  let spy;

  beforeAll(async () => {
    spy = jest.fn();
    spy = jest.spyOn(singleton, 'myPromise');
  });

  beforeEach(() => spy.mockReset());

  test('Check bar', () => {
    return singleton.myPromise().then((bar) => {
      expect(bar).toEqual('bar');
    });
  });

  test('Check called times', () => {
    singleton.myPromise();
    expect(spy).toHaveBeenCalledTimes(1);
  });

  afterAll(() => {
    jest.restoreAllMocks();
  });
});

The Check bar test is failing because the myPromise method doesn't return a promise:

the error

If I comment the spy = jest.spyOn(singleton, 'myPromise'); the test works.. but obviously the other test doesn't works..

enter image description here

I would expect that all the tests work with spyOn, because reading the docs is wrote:

Note: By default, jest.spyOn also calls the spied method.

I'm missing something?

Thanks for the help

like image 986
Manuel Spigolon Avatar asked Mar 02 '18 16:03

Manuel Spigolon


2 Answers

Here is working snippet without Jasmine:

describe('Module test-suite', () => {
  let spy;

  beforeAll(() => { // get rid of async
    spy = jest.fn();
    spy = jest.spyOn(singleton, 'myPromise');
  });

  afterEach(() => spy.mockRestore()); // main difference is here

  test('Check bar', () => {
    return singleton.myPromise().then((bar) => {
      expect(bar).toEqual('bar');
    });
  });

  test('Check called times', () => {
    singleton.myPromise();
    expect(spy).toHaveBeenCalledTimes(1);
  });

  afterAll(() => {
    jest.restoreAllMocks();
  });
});

I think it's better to use mockRestore instead of mockReset, because as I understand from jest docs mockReset hardly clears all information about spy and spying object. And mockRestore just clears some temp data such as number of called times.

Why is using afterEach instead of beforeEach important - I have no clue :(

Also I removed async because tests failed with it.

like image 68
Andrew Miroshnichenko Avatar answered Nov 07 '22 20:11

Andrew Miroshnichenko


I can't see any mention of this in the documentation but it seems like jest allows you to use a lot of Jasmine's functionality / syntax, including .and.callThrough:

beforeEach(() => {
  spyOn(singleton, 'myPromise').and.callThrough();
};

test('Check bar', async () => {
  const bar = await singleton.myPromise();
  expect(bar).toEqual('bar');
});

test('Check called times', () => {
  singleton.myPromise();
  expect(singleton.myPromise.calls.count()).toBe(1);
});

Here are the jasmine spy docs: https://jasmine.github.io/2.0/introduction.html#section-Spies

If you want to stick to the documented jest.spyOn API then you can instead set the spy inside of the test that uses it, so that it doesn't affect the other:

test('Check bar', async () => {
  const bar = await singleton.myPromise();
  expect(bar).toEqual('bar');
});

test('Check called times', () => {
  const spy = jest.spyOn(singleton, 'myPromise');
  singleton.myPromise();
  expect(spy).toHaveBeenCalledTimes(1);
});
like image 32
Billy Reilly Avatar answered Nov 07 '22 21:11

Billy Reilly