Sinon: Stub callsFake not working

Created on 30 Aug 2018  路  4Comments  路  Source: sinonjs/sinon

Description

I try to set stub fakes for a express middleware function and it's not replacing over.

What I'm trying (how to reproduce)

I'm trying to use sinon stubbing via callsFake function, just as it's advised from their most updated docs.

Even though I'm requiring the module and replacing the function from the property at the export. I keep seeing the original function behavior acting.

I know that I should try to get the function stubbed before the middleware functions get setup, and that's when express app is first imported.

This is the function I'm trying to stub, defined as a function and exported as a object too. It's defined in a script file with a path like api/middlewares/stripe/signature.

const stripeHelper = require('../../../lib/stripe')
const logger = require('../../../lib/logger')
const verifySignature = (req, res, next) => {
  var event
  let eventName = req.url.replace('/', '')
  try {
      // Try adding the Event as `request.event`
    event = stripeHelper.signatureCheck(
        eventName,
        req.body,
        req.headers['stripe-signature']
      )
  } catch (e) {
      // If `constructEvent` throws an error, respond with the message and return.
    logger.error('Error while verifying webhook request signature', e.message, e)
    return res.status(400).send('Webhook Error:' + e.message)
  }
  req.event = event
  next()
}
module.exports.verifySignature = verifySignature

What I tried already

  • Use decache to make sure the express app instance is pristine and it's not being initialized with previous original middleware
  • Set multiple beforEach hooks in order to organize my stubs and preconditions or test

What keeps happening

  • The original middleware function gets executed
  • I don't see any logs of the stub functions (as second proof that sinon stub is not working

This is my stubs and test hooks setup:

const chai = require('chai')
const chaiHttp = require('chai-http')
const dirtyChai = require('dirty-chai')
const sinon = require('sinon')
const decache = require('decache')
const signatureMiddleware = require('../../../api/middlewares/stripe/signature')
const bp = require('body-parser')
let verifySignatureStub, rawStub

chai.should()
chai.use(dirtyChai)
chai.use(chaiHttp)

const API_BASE = '/api/subscriptions'
const planId = 'NYA-RUST-MONTHLY'
const utils = require('../../utils')
const {
  hooks: {createSubscription, emitPaymentSucceeded},
  stripe: {generateEventFromMock}
} = utils

let testUser, testToken, testSubscription, server

describe.only('Subscriptions renewal (invoice.payment_succeeded)', function () {
  this.timeout(30000)

  beforeEach(function (done) {
    createSubscription(server, {planId}, function (err, resp) {
      if (err) return done(err)
      const {user, jwt, subscription} = resp
      console.log(user, jwt)
      testUser = user
      testToken = jwt
      testSubscription = subscription
      done()
    })
  })

  beforeEach(function (done) {
    verifySignatureStub = sinon.stub(signatureMiddleware, 'verifySignature')
    rawStub = sinon.stub(bp, 'raw')
    rawStub.callsFake(function (req, res, next) {
      console.log('bp raw')
      return next()
    })
    verifySignatureStub.callsFake(function (req, res, next) {
      const {customerId} = testUser.stripe
      const subscriptionId = testSubscription.id
      console.log('fake verify')
      req.event = generateEventFromMock('invoice.payment_failed', {subscriptionId, customerId, planId})
      return next()
    })
    done()
  })

  beforeEach(function (done) {
    decache('../../../index')
    server = require('../../../index')
    const {customerId} = testUser.stripe
    const {id: subscriptionId} = testSubscription
    console.log(`emitting payment succeeded with ${customerId}, ${subscriptionId} ${planId}`)
    emitPaymentSucceeded(server, testToken, function (err, response) {
      if (err) return done(err)
      done()
    })
  })

  afterEach(function (done) {
    verifySignatureStub.restore()
    done()
  })

  it('Date subscription will renew gets set to a valid number roughly one month', function () {
    // Not even getting here becasue calling the original function contains verifyMiddleware which should be replaced
  })

  it('Current period end is modified')

  it('An invoice for the new starting period is generated')

  it('Subscription status keeps active')
})

Context (please complete the following information):

All runs over Node 8 and I'm running tests with mocha and did a set up with dirty chai.

These are my dev dependencies:

  "devDependencies": {
    "base64url": "^2.0.0",
    "cross-env": "^5.0.5",
    "decache": "^4.4.0",
    "dirty-chai": "^2.0.1",
    "faker": "^4.1.0",
    "google-auth-library": "^0.12.0",
    "googleapis": "^23.0.0",
    "minimist": "^1.2.0",
    "mocha": "^5.2.0",
    "nodemon": "^1.12.0",
    "nyc": "^11.2.1",
    "sinon": "^6.1.5",
    "standard": "^10.0.3",
    "stripe-local": "^0.1.1"
  }
stale

Most helpful comment

To other people finding this issue, I believe I spotted the reason why sinon was not working when I encountered this same issue (stubs not working at all) and came up with the following steps when using sinon for testing an express server + chai:

  1. Always clean up app imports first: decache('../../path/to/express/index.js');
  2. Create your stubs functionStub = sinon.stub(someImport, 'someFunction');
  3. Import your express server index file. app = require(''../../path/to/express/index.js');

After step 2, step 3 will pull up the stubs instead of the original functions.

What I believe happened in the OP's code snippet is that the order of 1 and 2 were switched, which means that he was creating the stubs, and then decache was clearing them.

This is definitely not an issue with sinon but more of module loading as @fatso83 mentioned.

The resulting code would look something like:

  beforeEach(async () => {
    // Step #1, clear cached imports
    decache('../../../index');

    // Step #2, create the stubs

    verifySignatureStub = sinon.stub(signatureMiddleware, 'verifySignature')
    rawStub = sinon.stub(bp, 'raw')
    rawStub.callsFake(function (req, res, next) {
      console.log('bp raw')
      return next()
    })
    verifySignatureStub.callsFake(function (req, res, next) {
      const {customerId} = testUser.stripe
      const subscriptionId = testSubscription.id
      console.log('fake verify')
      req.event = generateEventFromMock('invoice.payment_failed', {subscriptionId, customerId, planId})
      return next()
    })

    // Step #3, import server
    server = require('../../../index');

    // Now use server with the stubs
    createSubscription(server, {planId}, function (err, resp) {
      if (err) return done(err)
      const {user, jwt, subscription} = resp
      console.log(user, jwt)
      testUser = user
      testToken = jwt
      testSubscription = subscription
      done()
    })

    const {customerId} = testUser.stripe
    const {id: subscriptionId} = testSubscription
    console.log(`emitting payment succeeded with ${customerId}, ${subscriptionId} ${planId}`)
    emitPaymentSucceeded(server, testToken, function (err, response) {
      if (err) return done(err)
      done()
    })

    // Let the tests run using the stubs.
  })

Hope this helps someone!

All 4 comments

Try to create a mock of the desired modules with mock-require NPM's module:

https://www.npmjs.com/package/mock-require

By this way, whenever your code does a "require" it will actually import the "mocked" one (which by definition is a stub).

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

I'd be amazed if this was actually an issue of Sinon and not some detail of the module loading, with which Sinon has nothing to do. The setup is way too complicated to be meaningful in pointing to any bug, as well. I'd try rewire, proxyquire and the likes.


We are trying to keep the GitHub issues list tidy and focused on bugs and feature discussions. This ticket looks like a usage question; please post it to StackOverflow and tag it with sinon, so the bigger community can help answer your questions.

If you feel that your topic is an issue with Sinon, please open a new ticket and follow the guidelines for reporting an issue.

To other people finding this issue, I believe I spotted the reason why sinon was not working when I encountered this same issue (stubs not working at all) and came up with the following steps when using sinon for testing an express server + chai:

  1. Always clean up app imports first: decache('../../path/to/express/index.js');
  2. Create your stubs functionStub = sinon.stub(someImport, 'someFunction');
  3. Import your express server index file. app = require(''../../path/to/express/index.js');

After step 2, step 3 will pull up the stubs instead of the original functions.

What I believe happened in the OP's code snippet is that the order of 1 and 2 were switched, which means that he was creating the stubs, and then decache was clearing them.

This is definitely not an issue with sinon but more of module loading as @fatso83 mentioned.

The resulting code would look something like:

  beforeEach(async () => {
    // Step #1, clear cached imports
    decache('../../../index');

    // Step #2, create the stubs

    verifySignatureStub = sinon.stub(signatureMiddleware, 'verifySignature')
    rawStub = sinon.stub(bp, 'raw')
    rawStub.callsFake(function (req, res, next) {
      console.log('bp raw')
      return next()
    })
    verifySignatureStub.callsFake(function (req, res, next) {
      const {customerId} = testUser.stripe
      const subscriptionId = testSubscription.id
      console.log('fake verify')
      req.event = generateEventFromMock('invoice.payment_failed', {subscriptionId, customerId, planId})
      return next()
    })

    // Step #3, import server
    server = require('../../../index');

    // Now use server with the stubs
    createSubscription(server, {planId}, function (err, resp) {
      if (err) return done(err)
      const {user, jwt, subscription} = resp
      console.log(user, jwt)
      testUser = user
      testToken = jwt
      testSubscription = subscription
      done()
    })

    const {customerId} = testUser.stripe
    const {id: subscriptionId} = testSubscription
    console.log(`emitting payment succeeded with ${customerId}, ${subscriptionId} ${planId}`)
    emitPaymentSucceeded(server, testToken, function (err, response) {
      if (err) return done(err)
      done()
    })

    // Let the tests run using the stubs.
  })

Hope this helps someone!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

NathanHazout picture NathanHazout  路  3Comments

zimtsui picture zimtsui  路  3Comments

kbirger picture kbirger  路  3Comments

akdor1154 picture akdor1154  路  4Comments

andys8 picture andys8  路  4Comments