Search code examples
javascriptunit-testingmockingsinonsinon-chai

Unit testing dependency injections


I want to mock dependencies in a class but I cannot find the right way to achieve that. Using sinon for the unit testing. For example:

const { classB } = require("./classes/classB");
const { classC } = require("./classes/classC");
const sinon = require("sinon");

class classA {
    constructor() {
        this.classB = new classB();
        this.classC = new classC();
    }
 
    doSomething() {
        const data = this.classB.getInfo();
        const processed = processInfo(data);
        this.classC.processedData(processed);
        
    }
    
    processInfo(data) {
        // doesSomeProcessing
    }
}

module.exports = {
    classA
};

describe("classA", () => {
    describe("when instantiated", () => {
        it("calls classB's getInfo once, classC's processedData data once.", () => {
            sinon.stub(classB);
            let clsA = new classA();
            sinon.assert.calledOnce(classB.getInfo);
            sinon.assert.calledOnce(classC.processedData);
            // Also can we do something like this?
            // when classB.getinfo.then(provideMockData) 
            // so that we can mock the calls being made to avoid actual calls?
        });
    });
});

I tried doing research around this but could not find a workable solution. Any insight will be super helpful! Thanks in advance and apologies for any trouble caused!


Solution

  • You should stub out dependencies with Link Seams.

    E.g.

    classA.js:

    const { ClassB } = require('./classB');
    const { ClassC } = require('./classC');
    
    class ClassA {
      constructor() {
        this.classB = new ClassB();
        this.classC = new ClassC();
      }
    
      doSomething() {
        const data = this.classB.getInfo();
        this.classC.processedData(data);
      }
    }
    
    module.exports = { ClassA };
    

    classB.js:

    class ClassB {
      getInfo() {
        return 'real data';
      }
    }
    
    module.exports = { ClassB };
    

    classC.js:

    class ClassC {
      processedData(data) {
        return 'real process data';
      }
    }
    
    module.exports = { ClassC };
    

    classA.test.js:

    const sinon = require('sinon');
    const proxyquire = require('proxyquire');
    
    describe('66751571', () => {
      it('should pass', () => {
        const classBInstanceStub = {
          getInfo: sinon.stub().returns('teresa teng'),
        };
        const classCInstanceStub = {
          processedData: sinon.stub(),
        };
        const ClassBStub = sinon.stub().returns(classBInstanceStub);
        const ClassCStub = sinon.stub().returns(classCInstanceStub);
        const { ClassA } = proxyquire('./classA', {
          './classB': { ClassB: ClassBStub },
          './classC': { ClassC: ClassCStub },
        });
        const clsA = new ClassA();
        clsA.doSomething();
        sinon.assert.calledOnce(classBInstanceStub.getInfo);
        sinon.assert.calledWithExactly(classCInstanceStub.processedData, 'teresa teng');
      });
    });
    

    unit test result:

      66751571
        ✓ should pass (2326ms)
    
    
      1 passing (2s)
    
    -----------|---------|----------|---------|---------|-------------------
    File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    -----------|---------|----------|---------|---------|-------------------
    All files  |   81.82 |      100 |      50 |   81.82 |                   
     classA.js |     100 |      100 |     100 |     100 |                   
     classB.js |      50 |      100 |       0 |      50 | 3                 
     classC.js |      50 |      100 |       0 |      50 | 3                 
    -----------|---------|----------|---------|---------|-------------------