I'm trying to test the behavior of a particular route. It continues to run the middleware even when I create a stub. I want the event authentication to simply pass for now. I understand that it's not truly a "unit" test at this point. I'm getting there. I've also simplified the code a little. Here is the code to test:
const { rejectUnauthenticated } = require('../modules/event-authentication.middleware');
router.get('/event', rejectUnauthenticated, (req, res) => {
res.sendStatus(200);
});
Here is the middleware I am trying to skip:
const rejectUnauthenticated = async (req, res, next) => {
const { secretKey } = req.query;
if (secretKey) {
next();
} else {
res.status(403).send('Forbidden. Must include Secret Key for Event.');
}
};
module.exports = {
rejectUnauthenticated,
};
The test file:
const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
let app;
const authenticationMiddleware = require('../server/modules/event-authentication.middleware');
const { expect } = chai;
chai.use(chaiHttp);
describe('with correct secret key', () => {
it('should return bracket', (done) => {
sinon.stub(authenticationMiddleware, 'rejectUnauthenticated')
.callsFake(async (req, res, next) => next());
app = require('../server/server.js');
chai.request(app)
.get('/code-championship/registrant/event')
.end((err, response) => {
expect(response).to.have.status(200);
authenticationMiddleware.rejectUnauthenticated.restore();
done();
});
});
});
I've tried following other similar questions like this: How to mock middleware in Express to skip authentication for unit test? and this: node express es6 sinon stubbing middleware not working but I'm still getting the 403 from the middleware that should be skipped. I also ran the tests in debug mode, so I know the middleware function that should be stubbed is still running.
Is this an issue with stubbing my code? Is this an ES6 issue?
Can I restructure my code or the test to make this work?
There is an issue indeed with stubbing your code.
When you require your server file
const app = require('../server/server.js');
your app is get created with the whole set of middlewares, including rejectUnauthenticated
, and a reference to the latter is stored inside app
.
When you do
sinon.stub(authenticationMiddleware, 'rejectUnauthenticated')
.callsFake(async (req, res, next) => next());
you replace the rejectUnauthenticated
exported method of authenticationMiddleware
module, but not the reference to original rejectUnauthenticated
that is already stored.
The solution is to create the app (i.e. require('../server/server.js');
) after you mock the exoprted middleware method:
const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
// don't create app right away
let app;
const authenticationMiddleware = require('../server/modules/event-authentication.middleware');
const { expect } = chai;
chai.use(chaiHttp);
describe('with correct secret key', () => {
it('should return bracket', (done) => {
sinon.stub(authenticationMiddleware, 'rejectUnauthenticated')
.callsFake(async (req, res, next) => next());
// method is stubbed, you can create app now
app = require('../server/server.js');
chai.request(app)
.get('/code-championship/registrant/event')
.end((err, response) => {
expect(response).to.have.status(200);
authenticationMiddleware.rejectUnauthenticated.restore();
done();
});
});
});
Based on @Sergey's suggestion, I did switch to Jest. At least for this specific case, it greatly simplified the implementation. For those interested, here is the end result:
const express = require('express');
const request = require('supertest');
const registrantRouter = require('../server/routers/registrant.router');
jest.mock('../server/modules/event-authentication.middleware');
const { rejectUnauthenticated } = require('../server/modules/event-authentication.middleware');
const initRegistrantRouter = () => {
const app = express();
app.use(registrantRouter);
return app;
};
describe('GET /registrant', () => {
test('It should 200 if event authentication passes', async (done) => {
const app = initRegistrantRouter();
rejectUnauthenticated.mockImplementation((req, res, next) => next());
const res = await request(app).get('/event');
expect(res).toHaveProperty('status', 200);
done();
});
test('It should 403 if event authentication fails', async (done) => {
const app = initRegistrantRouter();
rejectUnauthenticated.mockImplementation((req, res) => res.sendStatus(403));
const res = await request(app).get('/event');
expect(res).toHaveProperty('status', 403);
done();
});
});
Thanks also to this helpful blog post about testing express apps with Jest: https://codewithhugo.com/testing-an-express-app-with-supertest-moxios-and-jest/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With