Reputation: 5563
I'd like to be able to stub my middleware functions on a per-test basis. The problem, as articulated here, is that I can't just stub my middleware functions since node has cached the middleware function so I can't stub it since I create my app at the very beginning.
const request = require("supertest");
const { expect } = require("chai");
const sinon = require('sinon');
const auth = require ("../utils/auth-middleware")
const adminStub = sinon.stub(auth, "isAdmin").callsFake((req, res, next) => next());
const app = require("../app.js"); // As soon as I create this, the middleware isAdmin function becomes cached and no longer mutable
The above works as the solution described in the linked SO answer, but I don't like that in order to restore the stub or modify the fake, I have to completely recreate the server.
I'm wondering if there's a better, elegant way to work around the fact that Node caches these functions upon first require
. I was looking into maybe using proxyquire
or decache
but both seem to provide workarounds rather than sustainable solutions (although I may very well be wrong here).
Upvotes: 2
Views: 3292
Reputation: 183
I did the next implementation with typescript. I loaded the ./app
(express) dynamically within the before hook. I had the same issue that @Sebastian Wojdanowski is mentioning when running with multiple test files.
// test.spec.ts
import middleware from "../middleware/middle";
import sinon from "sinon";
import * as supertest from "supertest";
describe("user controller", ()=> {
let request: supertest.SuperAgentTest;
let myMiddle;
before(() => {
myMiddle = sinon.stub(middleware, "isAdmin");
myMiddle.callsFake(async (_req, _res, next)=> next());
import("../app").then(async app => {
request = await supertest.agent(app.default);
});
});
after(()=> {
// This restores the stubs for the next test file.
// Without this, it returns the next error message
// Attempted to wrap isAdmin which is already wrapped
sinon.verifyAndRestore();
});
describe("Next test suites", ()=> {
});
});
Upvotes: 0
Reputation: 11
Sergey Lapin solution worked great for me for single file test. If you are running multiple test files (with mocha for example) and one of them is stubbing the middleware like Sergey Lapin suggested - callsFake does not work.
Solution: test_config.js
const auth= require('../utils/auth-middleware');
const sinon = require('sinon');
const originalIsAdmin = auth.isAdmin;
const stubbedIsAuth = sinon.stub(auth, 'isAdmin');
const app = require("../app.js");
module.exports = {
originalIsAdmin,
stubbedIsAuth,
app,
};
Inside file A.js whare you need to skip isAdmin:
const { app } = require('./test_config');
const auth= require('../utils/auth-middleware');
// before each test. No other .stub
beforeEach(function () {
auth.isAdmin.callsFake((req, res, next) => next());
});
Inside file B.js whare you need original implementation of isAdmin:
const { originalIsAdmin, app} = require('./test_config');
// before each test. No other .stub
beforeEach(function () {
auth.isAdmin.callsFake(originalIsAdmin);
});
Upvotes: 1
Reputation: 2693
The problem is not really related to Node caching modules - it's express who stores a reference to a middleware function when a server is initially created.
After isAdmin
method of require
d module is stubbed, it's the cached version who gets stubbed, so using a tool like proxyquire
would only allow you to require a fresh version of the module (without stubbed method) if you need it for some reason.
If what you're looking for is adjusting behavior of a particular middleware for already created express server, you'd need a way to alter middleware function's behavior by its reference. Hopefully, sinon stubs (and others too, e.g. ones jest provides) are capable of that. However, you still need to stub the module before creating an express server so it stores a reference to the stubbed function.
Sample implementation might look as follows:
const request = require("supertest");
const { expect } = require("chai");
const sinon = require('sinon');
const auth = require ("../utils/auth-middleware");
// store reference to original function in case you need it:
const originalIsAdmin = auth.isAdmin
// replace isAdmin method with a stubbed, but don't specify implementation yet
const adminStub = sinon.stub(auth, "isAdmin");
// init express server that relies on stubbed `auth.isAdmin` reference
const app = require("../app.js");
it('this test is using auth.isAdmin that just calls .next()', () => {
// make middleware just pass
auth.isAdmin.callsFake((req, res, next) => next());
// ...
});
it('this test is using real auth.isAdmin implementation', () => {
// make middleware call real implementation
auth.isAdmin.callsFake(originalIsAdmin);
// ...
});
Upvotes: 11