Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

nodejs - stub module.exports functions with sinon

I have an expressjs app with the following routes and middleware modules. I am trying to test the routes module using mocha, chai, http-chai and sinonjs. The API uses mysql and in order to test the routes module, I have it all modularized so that I can stub out the mysql module. However when I try to stub middleware/index, I am having trouble. If I try to require index normally, the module doesn't actually get stubbed. If I try to require it using require.cache[require.resolve('./../../lib/routes/middleware/index')];, it seems to stub something, but indexStub.returns(indexObj) returns an error TypeError: indexStub.returns is not a function and TypeError: indexStub.restore is not a function.

How do I stub out index.js properly in order to control the code flow and keep it from trying to connect to mysql?

routes.js

'use strict';

const express =      require('express');
const router =       express.Router();
const configs =      require('./../config/configs');
const middleware =   require('./middleware/index');
const bodyParser =   require('body-parser');

const useBodyParserJson = bodyParser.json({
  verify: function (req, res, buf, encoding) {
    req.rawBody = buf;
  }
});

const useBodyParserUrlEncoded = bodyParser.urlencoded({extended: true});

// creates a new post item and return that post in the response
router.post('/posts', useBodyParserUrlEncoded, useBodyParserJson, middleware.validatePostData, middleware.initializeConnection, middleware.saveNewPost, middleware.closeConnection, function(req, res) {
  if (res.statusCode === 500) {
    return res.send();
  }
  if (res.statusCode === 405) {
    return res.send('Item already exists with slug ' + req.body.slug + '. Invalid method POST');
  }
  res.json(res.body).end();
});

module.exports = router;

middleware/index.js

'use strict';

const configs =  require('./../../config/configs');
const database = require('./../../factories/databases').select(configs.get('STORAGE'));
const dataV = require('./../../modules/utils/data-validator');


module.exports = {
  initializeConnection: database.initializeConnection, // start connection with database
  closeConnection:      database.closeConnection,      // close connection with database
  saveNewPost:          database.saveNewPost,          // creates and saves a new post
  validatePostData:     dataV.validatePostData,        // validates user data
};

spec-routes.js

'use strict';

var chai = require('chai');
var chaiHttp = require('chai-http');
var sinonChai = require("sinon-chai");
var expect = chai.expect;
var sinon = require('sinon');
chai.use(sinonChai);
chai.use(chaiHttp);
var app = require('./../../app');

  describe('COMPLEX ROUTES WITH MIDDLEWARE', function() {
    var indexM = require.cache[require.resolve('./../../lib/routes/middleware/index')];

    describe('POST - /posts', function() {
      var indexStub,
        indexObj;

      beforeEach(function() {
        indexStub = sinon.stub(indexM);
        indexObj = {
          'initializeConnection': function(req, res, next) {
            return next();
          },
          'closeConnection': function(req, res, next) {
            return next();
          },
          'validatePostData': function(req, res, next) {
            return next();
          }
        };
      });

      afterEach(function() {
        indexStub.restore();
      });

      it('should return a 500 response', function(done) {
        indexObj.saveNewPost = function(req, res, next) {
          res.statusCode = 500;
          return next();
        };
        indexStub.returns(indexObj);
        chai.request(app)
          .post('/posts')
          .send({'title': 'Hello', 'subTitle': 'World', 'slug': 'Example', 'readingTime': '2', 'published': false})
          .end(function(err, res) {
            expect(res).to.have.status(500);
            done();
          });
      });
    });
  });
like image 876
hyprstack Avatar asked Feb 23 '17 14:02

hyprstack


People also ask

What does Sinon stub return?

The sinon. stub() substitutes the real function and returns a stub object that you can configure using methods like callsFake() . Stubs also have a callCount property that tells you how many times the stub was called.

How do I stub a Sinon dependency?

To stub a dependency (imported module) of a module under test you have to import it explicitly in your test and stub the desired method. For the stubbing to work, the stubbed method cannot be destructured, neither in the module under test nor in the test.

How do you mock a function in Sinon?

var mock = sinon. Creates a mock for the provided object. Does not change the object, but returns a mock object to set expectations on the object's methods.

What is the function of library Sinon?

Sinon JS is a popular JavaScript library that lets you replace complicated parts of your code that are hard to test for “placeholders,” so you can keep your unit tests fast and deterministic, as they should be.


1 Answers

You don't use Sinon at all, as it doesn't deal with module loading at all. I see you have started doing this manually using the internal Node API's, but I suggest you do it the way we advise in the Sinon docs regarding this usecase: juse use proxyquire.

It enables you to substitute require calls to ./middleware/index.js for a mock object of your own liking (possibly made using sinon).

You would use it something like this:

var myIndex = { 
  initializeConnection: sinon.stub(),
  closeConnection:      sinon.stub(),
  saveNewPost:          sinon.stub()
};
var app = proxyquire('./../../app', {'./middleware/index': myIndex});
like image 110
oligofren Avatar answered Oct 06 '22 02:10

oligofren