0

For some reason I am having a little trouble getting this simple test to run correctly with a similar set-up I have used multiple times before.

Perhaps a fresh pair of eyes could help me understand why my method generateReport is not being invoked and none of my stubs are being triggered with the intended arguments?

BYE nor GOOD are ever logged and the tests are returning the error: AssertError: expected stub to be called with arguments

My index file:

const errorHandler = require('./lib/handlers/error-handler')
const transformRequest = require('./lib/handlers/request-converter')
const convert = require('./lib/convert')

exports.generateReport = function generateReport(req, res) {
  console.log('HELLO')
  const objectToPopulateTemplate = transformRequest(req.body)
  convert(objectToPopulateTemplate, function (e, data) {
    if (e) {
      console.log('BYE')
      const error = errorHandler(e)
      return res.send(error.httpCode).json(error)
    }
    console.log('GOOD')
    res
      .set('Content-Type', 'application/pdf')
      .set('Content-Disposition', `attachment; filename=velocity_report_${new Date()}.pdf`)
      .set('Content-Length', data.length)
      .status(200)
      .end(data)
  })
}

My test file:

const proxyquire = require('proxyquire')
const assert = require('assert')
const sinon = require('sinon')
const fakeData = require('./data/sample-request.json')


describe('report-exporter', () => {
  describe('generateReport', () => {
    const fakeError = new Error('Undefined is not a function')

    let res, resSendStub, resStatusStub, resEndStub, resSetStub, resJsonStub, req, convertStub, generate
    before(() => {
      resSendStub = sinon.stub()
      resJsonStub = sinon.stub()
      resStatusStub = sinon.stub()
      resEndStub = sinon.stub()
      resSetStub = sinon.stub()
      convertStub = sinon.stub()

      res = {
        send: function(errorCode) {
          return resSendStub(errorCode)
        },
        json: function(object) {
          return resJsonStub(object)
        },
        set: function(arg1, arg2) {
          return resSetStub(arg1, arg2)
        },
        status: function(code) {
          return resStatusStub(code)
        },
        end: function(data) {
          return resEndStub(data)
        }
      }
      req = {
        body: {}
      }

      generate = proxyquire('./../index', {
        './lib/convert': function() {
          return convertStub
        }
      })
    })


    it('Should return an error response', () => {
      convertStub.throws(fakeError)
      generate.generateReport(req, res)
      sinon.assert.calledWith(resSendStub, '500')
    })
  })
})
hyprstack
  • 4,043
  • 6
  • 46
  • 89

2 Answers2

0

It looks like you are proxyquireing your ./lib/convert incorrectly. Original convert is called with objectToPopulateTemplate and a callback function (e, data). And that's the callback who is responsible for error handling and sending a response.

The stubbed convert function though doesn't care about about the provided callback at all, so the callback never gets called.

Most likely what you want is to pass the parameters to convertStub and deal with them later:

'./lib/convert': function(objectToPopulateTemplate, cb) {
    return convertStub(objectToPopulateTemplate, cb);
}

and then

it('Should return an error response', () => {
  generate.generateReport(req, res);
  const cb = convertStub.getCall(0).args[1];

  // simulate `convert` to fail
  cb(fakeError);

  sinon.assert.calledWith(resSendStub, '500')
})
Sergey Lapin
  • 2,633
  • 2
  • 18
  • 20
0

Here is the unit test solution:

index.js:

const errorHandler = require("./error-handler");
const transformRequest = require("./request-converter");
const convert = require("./convert");

exports.generateReport = function generateReport(req, res) {
  console.log("HELLO");
  const objectToPopulateTemplate = transformRequest(req.body);
  convert(objectToPopulateTemplate, function(e, data) {
    if (e) {
      console.log("BYE");
      const error = errorHandler(e);
      return res.send(error.httpCode).json(error);
    }
    console.log("GOOD");
    res
      .set("Content-Type", "application/pdf")
      .set(
        "Content-Disposition",
        `attachment; filename=velocity_report_${new Date()}.pdf`
      )
      .set("Content-Length", data.length)
      .status(200)
      .end(data);
  });
};

error-handler.js:

module.exports = function(error) {
  return error;
};

request-converter.js:

module.exports = function transformRequest(body) {
  return body;
};

convert.js:

module.exports = function convert(body, callback) {
  callback();
};

index.spec.js:

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

describe("report-exporter", () => {
  describe("generateReport", () => {
    afterEach(() => {
      sinon.restore();
    });
    const fakeError = new Error("Undefined is not a function");
    fakeError.httpCode = 500;

    it("Should return an error response", () => {
      const logSpy = sinon.spy(console, "log");
      const mReq = { body: {} };
      const mRes = { send: sinon.stub().returnsThis(), json: sinon.stub() };
      const convertStub = sinon.stub();
      const errorHandlerStub = sinon.stub().returns(fakeError);
      const transformRequestStub = sinon.stub().returns(mReq.body);
      const generate = proxyquire("./", {
        "./convert": convertStub,
        "./error-handler": errorHandlerStub,
        "./request-converter": transformRequestStub
      });

      generate.generateReport(mReq, mRes);
      convertStub.yield(fakeError, null);
      sinon.assert.calledWith(transformRequestStub);
      sinon.assert.calledWith(convertStub, {}, sinon.match.func);
      sinon.assert.calledWith(errorHandlerStub, fakeError);
      sinon.assert.calledWith(logSpy.firstCall, "HELLO");
      sinon.assert.calledWith(logSpy.secondCall, "BYE");
    });
  });
});

Unit test result with coverage report:

  report-exporter
    generateReport
HELLO
BYE
      ✓ Should return an error response (94ms)


  1 passing (98ms)

----------------------|----------|----------|----------|----------|-------------------|
File                  |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------------------|----------|----------|----------|----------|-------------------|
All files             |     87.5 |       50 |     62.5 |     87.5 |                   |
 convert.js           |       50 |      100 |        0 |       50 |                 2 |
 error-handler.js     |       50 |      100 |        0 |       50 |                 2 |
 index.js             |    84.62 |       50 |      100 |    84.62 |             14,15 |
 index.spec.js        |      100 |      100 |      100 |      100 |                   |
 request-converter.js |       50 |      100 |        0 |       50 |                 2 |
----------------------|----------|----------|----------|----------|-------------------|

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

Lin Du
  • 88,126
  • 95
  • 281
  • 483