Search code examples
javascriptnode.jsunit-testingchaisinon

How to stub an object returned by function exported in the module


consider I have the following:

Note: The examples shown below just illustrate an idea for brevity and doesn't consider real world use case

File: simpleCalcs.js

module.exports = function () {
    return {
        sum: (listOfNumbers) => {
            console.log('inside test2 sum()');
            return listOfNumbers.reduce((a, b) => a + b, 0);
        }
    }
};

File: mediumCalcs.js

const simpleCalcs = require('./simpleCalcs')();

module.exports = function () {
    return {
        avg: (listOfNumbers) => {
            console.log('inside test1 avg()')
            return simpleCalcs.sum(listOfNumbers) / listOfNumbers.length;
        }
    }
};

File: mediumCalcs.test.js

const sinon = require('sinon');
const expect = require('chai').expect;
const simpleCalcs = require('./simpleCalcs')();
const mediumCalcs = require('./mediumCalcs')();

describe('sample test', () => {

    before(() => {
        summationStub = sinon.stub(simpleCalcs, 'sum').returns(3);
    });

    it('average', () => {
        const result = mediumCalcs.avg([1, 2, 3]);
        expect(result).to.be.equal(1);
    });
});

This test fails as the stubbed function is not called.

How do I ensure that the module.exports = function () {....} is taken care of while stubbing?

Is sinon capable of handling such requirements??

P.S The reason module.exports = function () {....} is used instead of module.exports = {....} is because one can inject some dependencies via params.


Solution

  • You can use link seams, so I will use proxyquire to stub it.

    E.g.

    simpleCalcs.js:

    module.exports = function() {
      return {
        sum: (listOfNumbers) => {
          console.log('inside test2 sum()');
          return listOfNumbers.reduce((a, b) => a + b, 0);
        },
      };
    };
    

    mediumCalcs.js:

    const simpleCalcs = require('./simpleCalcs')();
    
    module.exports = function() {
      return {
        avg: (listOfNumbers) => {
          console.log('inside test1 avg()');
          return simpleCalcs.sum(listOfNumbers) / listOfNumbers.length;
        },
      };
    };
    

    mediumCalcs.test.js:

    const sinon = require('sinon');
    const expect = require('chai').expect;
    const proxyquire = require('proxyquire');
    
    describe('sample test', () => {
      it('average', () => {
        const simpleCalcsStub = {
          sum: sinon.stub().returns(3),
        };
        const simpleCalcs = sinon.stub().callsFake(() => simpleCalcsStub);
        const mediumCalcs = proxyquire('./mediumCalcs.js', {
          './simpleCalcs': simpleCalcs,
        })();
        const result = mediumCalcs.avg([1, 2, 3]);
        expect(result).to.be.equal(1);
        sinon.assert.calledOnce(simpleCalcs);
        sinon.assert.calledWithExactly(simpleCalcsStub.sum, [1, 2, 3]);
      });
    });
    

    unit test results with coverage report:

      sample test
    inside test1 avg()
        ✓ average (1613ms)
    
    
      1 passing (2s)
    
    ----------------|---------|----------|---------|---------|-------------------
    File            | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ----------------|---------|----------|---------|---------|-------------------
    All files       |      60 |      100 |      40 |   66.67 |                   
     mediumCalcs.js |     100 |      100 |     100 |     100 |                   
     simpleCalcs.js |      20 |      100 |       0 |      25 | 2,4,5             
    ----------------|---------|----------|---------|---------|-------------------
    

    source code: https://github.com/mrdulin/expressjs-research/tree/master/src/stackoverflow/60746613