Reputation: 120
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
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:
routes.js
file is required and it loads all the dependenciesverifyGipToken.js
file is loaded too and stored in the require.cache
objectverifyGipToken
function modifying the require.cache
objectverifyGipToken
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