2

I'm trying to stub auth.session when testing endpoint /allowUser2 on an express server app.js.

//--auth.js--
module.exports.session = (req, res, next) => {
  req.user = null;
  next();
};
//--app.js--
const express = require('express');

const auth = require('./auth');

const app = express();
app.use(auth.session);
app.get('/allowUser2', (req, res) => {
  if (!req.user) return res.status(401).send();
  if (req.user.user === 2) return res.status(200).send();
});

app.listen(4001).on('listening', () => {
  console.log(`HTTP server listening on port 4001`);
});

module.exports = app;

If I just have this one test file test1.js in my test suite, auth gets stubbed successfully.

//--test1.js--
let app;
const sinon = require('sinon');
const auth = require('../../auth.js');
const chai = require('chai');
const chaiHttp = require('chai-http');
const { expect } = chai;

chai.use(chaiHttp);
let agent;
describe('should allow access', () => {
  before(async () => {
    // delete require.cache[require.resolve('../../app.js')]; // causes Error: listen EADDRINUSE: address already in use
    sinon.stub(auth, 'session').callsFake((req, res, next) => {
      req.user = { user: 1 };
      next();
    });
    app = require('../../app.js');
    agent = chai.request.agent(app);
  });

  after(async () => {
    auth.session.restore();
  });
  it('should not allow access', async function () {
    const response = await agent.get('/allowUser2');
    expect(response.status).to.be.equal(200);
  });
});

However, if I have more than one test file that requires app.js then I have a problem. If app.js was already required in another test file, such as test2.js below, node doesn't reload app.js when it's required again in test1.js. This causes app.js to call the old auth.session function, not the new stubbed one. So the user isn't authenticated and the test fails.

//--test2.js--
const chai = require('chai');
const chaiHttp = require('chai-http');
const app = require('../../app.js');

const { expect } = chai;

chai.use(chaiHttp);
const agent = chai.request.agent(app);
describe('route /allowUser2', () => {
  it("shouldn't allow access", async function () {
    const response = await agent.get('/allowUser2');
    expect(response.status).to.be.equal(401);
  });
});

I tried to reload the app.js by using delete require.cache[require.resolve('../../app.js')];. This worked when reloading a file with a plain function, but when the file is a server like app.js this causes an error: Error: listen EADDRINUSE: address already in use.

Recreate:

  1. download Repo
  2. npm i
  3. npm test

How do you stub a function on the server?

Dashiell Rose Bark-Huss
  • 2,173
  • 3
  • 28
  • 48

3 Answers3

0

UPDATE: Proposed Solution https://github.com/DashBarkHuss/mocha_stub_server/pull/1

One problem is the way you are using a direct method reference in app.js prevents Sinon from working. https://gist.github.com/corlaez/12382f97b706c964c24c6e70b45a4991

The other problem (address in use) is because each time we want to get a reference to app, we are trying to create a server in the same port. Breaking that app/server creation into a separate step alleviates that issue.

corlaez
  • 1,352
  • 1
  • 15
  • 30
0

One solution is turn app.js into a function that starts the server on a port number passed in as an argument. Then change the port randomly when requiring. I do not like this option because there may be some reason to keep the app on a specific port.

app.js

const express = require('express');

const auth = require('./auth');

module.exports = (port) => {
  const app = express();
  app.use(auth.session);
  app.get('/allowUser2', (req, res) => {
    if (!req.user) return res.status(401).send();
    if (req.user.user === 2) return res.status(200).send();
  });
  app.listen(port).on('listening', () => {
    console.log(`HTTP server listening on port ${port}`);
  });
  return app;
};

when requiring

    app = require('../../app.js')((Math.random() * 10000).toString().slice(0, 4));

Dashiell Rose Bark-Huss
  • 2,173
  • 3
  • 28
  • 48
0

Instead of exporting the app in app.js, I export a function that launches the server and returns the server instance and app. By exporting the server instance I have the ability to close the server. The app is needed to pass into chai. Make sure const app = express(); is in this function and not before it or it won't recreate.

const express = require('express');

const auth = require('./auth');

const port = 4000;
module.exports = () => {
  const app = express();
  app.use(auth.session);
  app.get('/allowUser2', (req, res) => {
    if (!req.user) return res.status(401).send();
    if (req.user.user === 2) return res.status(200).send();
  });
  app.post('/allowUser2', (req, res) => {
    if (!req.user) return res.status(401).send();
    if (req.user.user === 2) return res.status(200).send();
  });
  return {
    server: app.listen(port).on('listening', () => {
      console.log(`HTTP server listening on port ${port}`);
    }),
    app,
  };
};

Then in my tests I can launch the server in before and close the server in after in both tests.

let app;
const sinon = require('sinon');
const auth = require('../../auth.js');
const chai = require('chai');
const chaiHttp = require('chai-http');
const { expect } = chai;

chai.use(chaiHttp);
let server;
describe('route /allowUser2', () => {
  before(async () => {
    // delete require.cache[require.resolve('../../app.js')]; // causes an error: `Error: listen EADDRINUSE: address already in use`.
    sinon.stub(auth, 'session').callsFake((req, res, next) => {
      req.user = { user: 2 };
      next();
    });
    server = require('../../app.js')();
    agent = chai.request.agent(server.app);
  });

  after(async () => {
    server.server.close(() => {
      console.log('Http server closed.');
    });
    auth.session.restore();
  });
  it('should allow access', async function () {
    const response = await agent.get('/allowUser2');
    expect(response.status).to.be.equal(200);
  });
});


const chai = require('chai');
const chaiHttp = require('chai-http');
const { expect } = chai;

chai.use(chaiHttp);
let server;
let agent;
describe('route /allowUser2', () => {
  before(async () => {
    server = require('../../app.js')();
    agent = chai.request.agent(server.app);
  });

  after(async () => {
    server.server.close(() => {
      console.log('Http server closed.');
    });
  });
  it("shouldn't allow access", async function () {
    const response = await agent.get('/allowUser2');
    expect(response.status).to.be.equal(401);
  });
});

working repo

Dashiell Rose Bark-Huss
  • 2,173
  • 3
  • 28
  • 48