4

Suppose I have a the following module, as database.js

const initOptions = {}
const pgp = require('pg-promise')(initOptions)
const config = require('../../config')

const db = pgp({
  host: config.database.host,
  port: config.database.port,
  database: config.database.database,
  user: config.database.user,
  password: config.database.password
})

module.exports = db

And the following module as create.js

const db = require('./database')

function create (name) {
  return new Promise((resolve, reject) => {
      db.func('create', name)
      .then(data => {
        return resolve(data)
      })
      .catch(err => {
        return reject(err)
      })
  })
}

module.exports = create

I'm trying to run a unit test on create.js that will test that db.func is called with 'create' as first argument and 'name' as the second, but without actually needing to set up a database connection (So tests can run offline).

From what I can gather, this is what libraries like sinon.JS can be used for, so I tried creating a test and stubbed the db object.

const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')

chai.use(chaiAsPromised)

const sinon = require('sinon')

const expect = chai.expect

const create = require('./create.js')

describe('Test Module', () => {
  it('should test stuff', () => {
    const db = require('./database')
    const dbStub = sinon.stub(db, 'func').callsFake(Promise.resolve(null))
    expect(create('test').then).to.be.a('Function')
  })
})

However, it fails with

TypeError: Cannot redefine property: func

Most likely due to my limited exposure to sinon...

How do I go about stubbing (or maybe I need to mock?) the db function so that I can test it an ensure db.func was called?

MadsRC
  • 197
  • 1
  • 12
  • The property `func` of that object is not configurable, that means `sinon` can't replace the property with `Object.defineProperty`. – MinusFour Oct 20 '17 at 18:22
  • That's also what I could figure out from researching it. However, I'm looking to see if there's any other way to do this with pg-promise. It simply cannot be true that you can't do offline unit tests with pg-promise – MadsRC Oct 20 '17 at 18:24

1 Answers1

3

You can make the properties configurable by disabling locks with the no noLocking option in Initialization Options. This allows sinon to replace the properties:

const initOptions = { noLocking : true };

On a related note:

Your create function is creating an unnecessary promise wrapper, which is a promise anti-pattern. You should just return the result from db.func, which is a promise already:

function create(name) {
      return db.func('create', name);
}

Also callsFake takes a function and you are giving it a promise. Use returns instead:

const dbStub = sinon.stub(db, 'func').returns(Promise.resolve(null))

I'm having trouble setting the noLocking option. The docs state it can be set after initialization, however if I set it with db.$config.options.noLocking = true, the same error occurs. However, if I set it in the database.js init options it works fine.

From the author of pg-promise...

It is because at that point the noLocking can only affect tasks and transactions. And since the db level of the protocol is initiated only once, setting noLocking after the library's initialization doesn't effect it.

I have just updated the documentation to clarify it: This option is dynamic (can be set before or after initialization). However, changing it after the library's initialization will not affect Database objects that have already been created.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138
MinusFour
  • 13,913
  • 3
  • 30
  • 39
  • Thank you, this sounds like the right solution! however, I'v having trouble setting the noLocking option. The docs state it can be set after initialization, however if I set it with db.$config.options.noLocking = true, the same error occurs. However, if I set it in the database.js init options it works fine. Though, seting it in the database.js module wouldn't be optimal. any idea? – MadsRC Oct 20 '17 at 19:28
  • @MadsRC, I have no idea to be honest, I think it's unlikely you can set that option like that. Once the object is set to non-configurable there's no way anyone can reset it to configurable. – MinusFour Oct 20 '17 at 19:34
  • @MadsRC, what you could do is only add the option to your init options if your ENV is development. That way you can still lock it while in production. – MinusFour Oct 20 '17 at 20:00
  • That was what I did actually! Though even though I stub db.func to return Promise.resolve(nulll) (As you advised) in the test, when I call create, the db function actually still connects to the database and creates (I happened to have a database listening on localhost as I was testing). Is that expected behaviour, or should sinon.stub not overwrite the db.func referenced in create.js ? – MadsRC Oct 20 '17 at 20:09