Search code examples
javascriptnode.jsmocha.jssinon

Simple way to test middleware in Express without creating recreating server?


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).


Solution

  • 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 required 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);
    
      // ...
    });