Search code examples
javascriptunit-testingspy

My spy function is not getting called - how can I properly spy this function?


I'm trying to do call a function that is imported as a function independently in another function that I'm calling from my unit test. How can I get callcount of 1 on the functionIWantToSpy in this scenario?

auditEvent.js:

const { verify } = require('@mycompany/verifylib');
const { functionIWantToSpy } = require('@mycompany/another-lib');

const auditEvent = () => {
  verify();
  functionIWantToSpy();
};

module.exports = { auditEvent };

test:

const { verify } = require('@mycompany/verify-lib');
const { functionIWantToSpy } = require('@mycompany/another-lib');

describe('mytest', () => {

  let spiedFuntion;
  let verifyStub;

  beforeEach(() => {
    verifyStub = sinon.stub();
    ({auditEvent} = proxyquire('./auditEvent', {
      '@mycompny/verify-lib': {
        verify: verifyStub,
        '@noCallThru': true,
      },
    }));
    spiedFunction = sinon.spy(functionIWantToSpy);
  });

  it('should work'), async () => {

    auditEvent();
    expect(functionIWantToSpy).to.have.callCount(1);   // Getting callcount of 0 here...
  });
});

Solution

  • Spying involves replacing the function with a new function. You are replacing what is referred to by the identifier functionIWantToSpy so that it refers to a new spy function, instead of the original function referred to by require('@mycompany/another-lib').functionIWantToSpy. The code inside the module can't see your new spy function. The expression require('@mycompany/another-lib').functionIWantToSpy refers to the original function, unchanged.

    Because require caches results (i.e., only the first require("foo") executes foo, and any subsequent require("foo") summons up that same object returned by the first require call), you can modify the functionIWantToSpy method of the require('@mycompany/another-lib') object, using the two-argument form of sinon.spy:

    spiedFunction = sinon.spy(require('@mycompany/another-lib'), "functionIWantToSpy");
    

    You must do this before the property is ever accessed (and value stored) by the module being tested:

    verifyStub = sinon.stub();
    
    // IMPORANT: FIRST, spy the functionIWantToSpy property on the required object before importing auditEvent
    spiedFunction = sinon.spy(require('@mycompany/another-lib'), "functionIWantToSpy");
    
    ({auditEvent} = proxyquire('./auditEvent', {
      '@mycompny/verify-lib': {
        verify: verifyStub,
        '@noCallThru': true,
      },
    }));
    

    This should work because when the auditEvent module runs for the first time and gets to

    const { functionIWantToSpy } = require('@mycompany/another-lib');
    

    then require('@mycompany/another-lib').functionIWantToSpy will refer to the spy function.