Search code examples
node.jssinonsinon-chai

How to fake a directly required function


If I require a function, how do I stub/mock it in sinon?

const { getLastYearData } = require('../../services')

module.exports = async lastYearInfo => {
   return getLastYearData(lastYearInfo)
}

If I do as below, it works, but I can't get it to work without referencing the object.

const services = require('../../services')

module.exports = async lastYearInfo => {
   return services.getLastYearData(lastYearInfo)
}

And:

const { expect } = require('chai')
const lastYearFunction = require('./last-year-function')
const sinon = require('sinon')
const services = require('../../services')

it('should return stubbed values', async () => {
    sandbox
     .stub(services, 'getLastYearData')
     .callsFake(lastYearInfo => {
       return { Data: 4 }
     })

   let data = await lastYearFunction({test: 1})

   const expected = { 
     Data: 4
   }

   expect(data).to.deep.equal(expected)
 })

Solution

  • This is a limitation introduced by the way node modules work.

    This line:

    const lastYearFunction = require('./last-year-function')
    

    Executes the code in ./last-year-function.js, which in turn executes this line:

    const { getLastYearData } = require('../../services')
    

    Which executes the code in ../../services.js, and assigns the lastYearFunction property of the result to a new variable in module scope. All of this happens before sinon has your stub in place, so no matter what, getLastYearData in your module will refer to the original function returned by services.

    While it would be possible to move the line that requires ./last-year-function to sometime after the stub is created, I would not recommend doing so. For one, it would be impossible to restore the original function in the event you need to. Even worse, however--- require results are cached in Node, so anything that requires ./last-year-function in the same run would also use the stub. Or, would use the original, depending on the order things are run. It's spotty and gross, so I do not recommend it.

    Your best bet is actually to just do it the way you do it with your second sample. Reference it through the services object, and restore it in an afterEach or somesuch.

    There are libraries out there for proxying require calls, which would be the only way to make something like your first code sample work. One I have used in the past is proxyquire. Give it a shot if you want, but in my experience it's not worth the additional complexity.