sinonjs / sinon

Test spies, stubs and mocks for JavaScript.
https://sinonjs.org/
Other
9.61k stars 769 forks source link

Stub called inside Promise then() doesn't have updated .called properties #2520

Closed lozadaOmr closed 1 year ago

lozadaOmr commented 1 year ago

What did you expect to happen?

What actually happens

testPromise () {
  let user = this.services.secrets.write({ id: req.user._id })
  next(user)
  res.json(404)
  return 'stop'
}

How to reproduce

// middleware.js
'use strict'

class Middleware {
  constructor (model, services, utils) {
    this.services = services
    this.model = model
    this.utils = utils
  }

  testPromise () {
    return (req, res, next) => {
      return this.services.secrets.write({ id: req.user._id })
        .then(secret => {
          res.json(203)
          next(false)

          console.log(`within next --- `, next.called)
          console.log(`within res --- `, res.json.called)

          return 'ends here'
        })
    }
  }
}

module.exports = Middleware
// middleware.test.js
'use strict'

global.Promise = require('bluebird')

const sinon = require('sinon')
const mongoose = require('mongoose')

const Secrets = require('path/to/stubbed/class') // secrets.write()
const Middleware = require('path/to/Middleware/class')

describe(`DEMO`, function () {
  describe(`# testPromise ()`, function () {
    let sandbox = null
    let middleware = null

    let model = null
    let utils = null
    let services = null

    let req = null
    let res = null
    let next = null

    let findByIdStub = null
    let secretsWriteStub = null

    before(function () {
      sandbox = sinon.createSandbox()

      req = { user: { _id: '1234890' } }
      res = { json: sandbox.stub().returnsArg(0) }

      let userResult = { userSecret: 'N3Kz+' }

      findByIdStub = sandbox.stub(mongoose.Model, 'findById')
        .usingPromise(global.Promise)
        .withArgs(req.user._id).resolves(userResult)

      model = { findById: findByIdStub }

      secretsWriteStub = sandbox.stub(Secrets.prototype, 'write')
        .usingPromise(global.Promise).resolves('write stub secret')

      services = {
        secrets: { write: secretsWriteStub }
      }
      utils = {}
    })

    afterEach(function () {
      sandbox.restore()
    })

    it(`should run DEMO `, function (done) {
      middleware = new Middlware(model, services, utils)

      next = sandbox.stub().returnsArg(0)
      let middlewareCall = middleware.testPromise()(req, res, next)

      // assertion should go here but use console.log to prevent it from failing while debugging
      console.log(`next stub ====> `, next.called)
      console.log(`resJsonStub =====> `, res.json.called)
      console.log(`secretswrite ====>  `, secretsWriteStub.called)

      done()
    })
  })
})
lozadaOmr commented 1 year ago

middlewareCall is a promise

So I did access stubbed next.called, res.json.called, secretsWriteStub.called by adding a then block

middlewareCall
  .then(() => {
    // assertion now goes in here
    console.log(`next stub ====> `, next.called)
    console.log(`resJsonStub =====> `, res.json.called)
    console.log(`secretswrite ====>  `, secretsWriteStub.called)

    done()
  })

Only problem with this is that, when assertion fails I receive a an error, instead of telling me which assertion fails