Hamza Boularbah
Hamza Boularbah

Reputation: 120

Mock fastify auth plugin in node tap using sinon

I'm trying to mock the return value of a fastify-auth plugin using Sinon but seems like I am missing something. Here is my implementation:

// routes.js

const fastifyAuth = require('fastify-auth')
const gipAuth = require('./share/fastify/gip-bearer-auth-plugin')

async function restv2 (fastify) {
  fastify
    .register(gipAuth)
    .post('/test', require('./controllers/test').test)
}

module.exports = async function (fastify) {
  await fastify.register(fastifyAuth)
  return fastify
    .register(restv2, { prefix: '/v2' })
}
// gip-bearer-auth-plugin.js

const fp = require('fastify-plugin')

const { verifyGipToken } = require('../helpers/verifyGipToken')

const verifyGipTokenPlugin = async fastify =>
  fastify
    .addHook('onRequest', fastify.auth([verifyGipToken]))

module.exports = fp(verifyGipTokenPlugin)
// verifyGipToken.js
const AuthorizationError = require('../errors/authorization-error')

async function verifyGipToken (req) {
  throw new AuthorizationError('verifyGipToken is not implemented)
}

module.exports = { verifyGipToken }

and here is the part where I try to mock the module in the test file

// test.js
const t = require('tap')
const sinon = require('sinon')
const verifyGipToken = require('/path/to/verifyGipToken')


t.test('test case', async t => {
  const sandbox = sinon.createSandbox()

  t.beforeEach(async t => {
    sandbox.stub(verifyGipToken, 'verifyGipToken').callsFake(async req => {
      console.log('verifyGipToken stubb called')
      return true
    })
  })

  t.afterEach(async t => {
    sandbox.restore()
  })

and the part that I'm logging does not show in the console, I get only the result from the original code

Upvotes: 0

Views: 684

Answers (1)

Manuel Spigolon
Manuel Spigolon

Reputation: 12890

I have replicated your issue and code structure here: https://github.com/Eomm/blog-posts/pull/39/files I would like to write a blog post about it.

Anyway the issue is that you are mocking the wrong reference of the verifyGipToken function.

Mocking is not magic in CJS, it just manipulates the require.cache object. Here is a nice home-made mock function: https://github.com/fastify/fastify-cli/issues/453

What is happening in your code:

  • The routes.js file is required and it loads all the dependencies
  • So, the verifyGipToken.js file is loaded too and stored in the require.cache object
  • Then you mock the verifyGipToken function modifying the require.cache object
  • Then the test code runs, but it refers to the verifyGipToken function that is already loaded in the require.cache object, so it is not the mocked one!

Quick fix:

// gip-bearer-auth-plugin.js
const fp = require('fastify-plugin')

async function verifyGipTokenPlugin (fastify) {
  fastify.addHook('onRequest', fastify.auth([
    // this loads the verifyGipToken again
    require('./verifyGipToken').verifyGipToken // 🦠 fix
  ]))
}

module.exports = fp(verifyGipTokenPlugin)

By doing this, the require() function is called again, so the require.cache object is updated with the new reference of the verifyGipToken function (the mocked one) and the test will pass as expected.

Another solution is to avoid the destructuring of the verifyGipToken.js:

const fp = require('fastify-plugin')

const ref = require('./verifyGipToken') // 🦠 fix

async function verifyGipTokenPlugin (fastify) {
  fastify.addHook('onRequest', fastify.auth([
    ref.verifyGipToken
  ]))
}

module.exports = fp(verifyGipTokenPlugin)

This works because ref points to a value in the require.cache object, so the stub.mock modify the value and all its references will be updated too.

In summary, mocking is cool, but it is very important to understand how it works under the hood otherwise it can be very frustrating while debugging or writing tests. If your tests fail after a refactoring, it is very likely that you are mocking the wrong reference.

As a suggestion: I would avoid to mock anything that is not an HTTP call or it can be a source of endless side effects.

Upvotes: 2

Related Questions