简体   繁体   中英

How can I spy on a nested dependency in with sinon

I've managed to successfully test a basic express server, a function returning promises, and a basic sequelize setup to get my nose wet; but I am stuck on spies/stubs/mocks.

My fist hiccup is trying to check that glob has been called in an external module:

//in utils.js
var glob = require('glob');

module.exports = {
  funToTest: function (msg, callback) {
    console.log(msg);
    glob('*md', {
      cwd: 'files/'
    }, function (err, files) {
      console.log(files);
    });
    callback();
    callback();
  }
};

Using a mocha/chai/sinon/sinon-chai combination:

// in utils-test.js
var utils = require('utils.js');
var glob = require('glob');

describe('Utils', function () {
  describe('funToTest method', function () {
    const callback = sinon.spy();
    const globSpy = sinon.spy(glob);

    before(function (done) {
      utils.funToTest('Files:', callback);
      done();
    });

    // This PASSES fine
    it ('should call our callback twice', function () {
      expect(callback).to.have.been.calledTwice;
    });

    // This NOT SO MUCH
    it('should call glob once', function () {
      expect(globSpy).to.have.been.calledOnce;
    });
  )};
)};

The above fails with an assertion error:

AssertionError: expected glob to have been called exactly once, but it was called 0 times

So how do I spy on the glob dependency in utils.funToTest to see if gets called?

You're spying on the glob module itself, not the glob call within your funToTest method. The problem is that the glob call is an implementation detail and is not actually accessible from within your test. You'd need to pass in an argument for the glob callback and test that it was called with a spy or stub.

//in utils.js
var glob = require('glob');

module.exports = {
  funToTest: function (msg, globCb, callback) {
    glob('*md', {
      cwd: 'files/'
    }, globCb);
    callback();
    callback();
  }
};

// in utils-test.js
var utils = require('utils.js');
var glob = require('glob');

describe('Utils', function () {
  describe('funToTest method', function () {
    const callback = sinon.spy();
    const globCb = sinon.spy();

    const err = {err: 'Error'};
    const files = ['file1', 'file2'];

    before(function (done) {
      utils.funToTest('Files:', globCb, callback);
      done();
    });

    // Should still pass
    it ('should call our callback twice', function () {
      expect(callback).to.have.been.calledTwice;
    });

    // Passes with correct args
    it('should call glob once', function () {
      expect(globCb).to.have.been.calledOnce;
      // inspect the arg values with .calledWithArgs
      expect(globCb.calledWithArgs(err, files)).to.be.true;
      // inspect the arg values with .getCall(index) (actually grabs the call args on the first call to globSpy)
      expect(globCb.getCall(0).args[0]).to.equal(err);
      expect(globCb.getCall(0).args[1]).to.equal(files);
    });
  )};
)};

Here is the unit test solution using additional library proxyquire :

utils.js :

const glob = require("glob");

module.exports = {
  funToTest: function(msg, callback) {
    console.log(msg);
    glob(
      "*md",
      {
        cwd: "files/",
      },
      function(err, files) {
        console.log(files);
      },
    );
    callback();
    callback();
  },
};

utils.test.js :

const proxyquire = require("proxyquire");
const sinon = require("sinon");

describe("36477213", () => {
  afterEach(() => {
    sinon.restore();
  });
  it("should pass", () => {
    const logSpy = sinon.spy(console, "log");
    const files = ["a", "b"];
    const globStub = sinon.stub().yields(null, files);
    const utils = proxyquire("./utils.js", {
      glob: globStub,
    });
    const callback = sinon.stub();
    utils.funToTest("some message", callback);
    sinon.assert.calledWith(globStub, "*md", { cwd: "files/" }, sinon.match.func);
    sinon.assert.calledTwice(callback);
    sinon.assert.calledWith(logSpy.firstCall, "some message");
    sinon.assert.calledWith(logSpy.secondCall, files);
  });
});

Unit test result with 100% coverage:

  36477213
some message
[ 'a', 'b' ]
    ✓ should pass


  1 passing (24ms)

---------------|----------|----------|----------|----------|-------------------|
File           |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files      |      100 |      100 |      100 |      100 |                   |
 utils.js      |      100 |      100 |      100 |      100 |                   |
 utils.test.js |      100 |      100 |      100 |      100 |                   |
---------------|----------|----------|----------|----------|-------------------|

Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/36477213

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM