35

I want to test, if particular function was called in my test and with the correct parameters. From JEST documentation I'm not able to figure out, what is the correct way to do it.

Let's say I have something like this:

// add.js

function child(ch) {
   const t = ch + 1;
   // no return value here. Function has some other "side effect"
}


function main(a) {
  if (a == 2) {
    child(a + 2);
  }

  return a + 1;
}

exports.main = main;
exports.child = child;

Now in unit test:

1. I want to run main(1) and test that it returned 2 and child() was not called.

2. And then I want to run main(2) and thest that it returned 3 and child(4) was called exactly once.

I have something like this now:

// add-spec.js
module = require('./add');

describe('main', () => {

  it('should add one and not call child Fn', () => {
    expect(module.main(1)).toBe(2);
    // TODO: child() was not called
  });

  it('should add one andcall child Fn', () => {

    expect(module.main(2)).toBe(3);
    // TODO: child() was called with param 4 exactly once
    // expect(module.child).toHaveBeenCalledWith(4);
  });

});

I'm testing this in https://repl.it/languages/jest , so a working example in this REPL will be much appreciated.

DHlavaty
  • 12,958
  • 4
  • 20
  • 25

4 Answers4

22

OK, I've figured it out. The trick is, to split functions into separate files. So the code is (and works in https://repl.it/languages/jest ):

// add.js
child = require('./child').child;

function main(a) {
  if (a == 2) {
    child(a + 2);
  }

  return a + 1;
}

exports.main = main;

extracted child.js file:

// child.js

function child(ch) {
   const t = ch + 1;
   // no return value here. Function has some other "side effect"
}

exports.child = child;

main test file:

// add-spec.js
main = require('./add').main;
child = require('./child').child;

child = jest.fn();

describe('main', () => {

  it('should add one and not call child Fn', () => {
    expect(main(1)).toBe(2);

    expect(child).toHaveBeenCalledTimes(0);
  });

  it('should add one andcall child Fn', () => {
    expect(main(2)).toBe(3);

    expect(child).toHaveBeenCalledWith(4);
    expect(child).toHaveBeenCalledTimes(1);
  });

});
DHlavaty
  • 12,958
  • 4
  • 20
  • 25
  • 6
    If you are going to overwrite `child` with `jest.fn()`, then you might as well not require child, right? – dotnetCarpenter Sep 14 '18 at 17:47
  • 2
    @DHlavaty and the trick is to use `child = jest.fn();`, not to split functions into separate files, right? – Andrei Prigorshnev Jan 13 '19 at 13:06
  • Your main function is still not pure. It is using an outside variable `child` function inside the function. If you pass it as a parameter to the `main` function, then you can spy on `main` function and check if your `child` parameter is called or not. – zimmerbimmer May 28 '20 at 23:56
  • Anyway to do this using a spay instead of seeing child directly as a jest.fn – Tanner Summers Jan 06 '23 at 21:26
5
let child = require('./child');
let main = require('./add').main;

// name of the module, name of the function
spy = jest.spyOn(child, 'child');

describe('main', () => {

  it('should add one and call child Fn', () => {
    expect(main(1)).toBe(2);
    // Called or not
    expect(spy).toHaveBeenCalled();
  });



});
Artha Ali
  • 51
  • 1
  • 2
0

In my case I had a similar doubt with angular code so I have a method, that is invoked when a field in a form is changed, and the only task of this method is to trigger some other methods.

Code extract:

handleConnectionToLegChange(value) {
if (!isNullOrUndefined(value)) {
  this.connectionsForm.controls.to.markAsDirty();
  this.connectionsForm.controls.to.updateValueAndValidity();
  this.connectionsForm.controls.from.markAsDirty();
  this.connectionsForm.controls.from.updateValueAndValidity();
  this.updateModalButtonStatus(this.connectionsSubject);
}}

So in order to test it I used this test case. (I just spied on 2 of the 5 triggered methods but that's enough on my case.)

test extract:

 it('should execute fn handleConnectionToLegChange and check method calls if value is not null', () => {
component.connectionsForm.controls.to.updateValueAndValidity = jest.fn();
component.updateModalButtonStatus = jest.fn();
component.handleConnectionToLegChange('a');
expect(component.connectionsForm.controls.to.updateValueAndValidity).toHaveBeenCalled();
expect(component.updateModalButtonStatus).toHaveBeenCalled(); });

It worked fine for me.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Adrian Cubillos
  • 1
  • 1
  • 1
  • 2
0

To be mocked:

// child.js
function child(ch) {
  console.log('some side effects happen in here', ch);
}

exports.child = child;

To be tested:

// main.js
const { child } = require('./child');

function main(a) {
  if (a == 2) {
    child(a + 2);
  }

  return a + 1;
}

exports.main = main;

Test for main.js

// main.test.js
jest.mock('./child');
const { main } = require('./main');

// This is the mocked version of "child"
const { child } = require('./child');

describe('main', () => {

  it('should add one and not call child Fn', () => {
    expect(main(1)).toBe(2);

    expect(child).toHaveBeenCalledTimes(0);
  });

  it('should add one and call child Fn', () => {
    expect(main(2)).toBe(3);

    expect(child).toHaveBeenCalledWith(4);
    expect(child).toHaveBeenCalledTimes(1);
  });
});
Steve
  • 1,159
  • 10
  • 14