I am attempting to unit test my authentication middleware for Express. The middleware is quite simple and can be viewed below in its entirety:
const admin = require('./../config/firebase/firebase');
// Models - User
const User = require('./../models/user');
const auth = async (req, res, next) => {
try {
// The Authorization Bearer Token sent in the header of the request needs to be decoded.
const token = req.header('Authorization').replace('Bearer ', '');
const decoded = await admin.auth().verifyIdToken(token);
// Finding that user in the database by their Firebase UID.
const user = await User.findOne({ _id: decoded.uid });
// If that user does not exist, we'll throw an error.
if (!user) {
throw new Error();
}
// Making the user accessible to the endpoint.
req.user = user;
// Proceed
next();
} catch (e) {
// HTTP 404 Unauthorized Response Status
res.status(401).send({ error: 'Please authenticate.' });
}
}
module.exports = auth;
Since the Firebase Admin SDK returns an object that contains the user's UID as a property, for the purpose of my tests, I create a "fake token" which is just an object with a UID property. I then mock the Admin SDK such that it returns whatever was passed in, as so:
module.exports = {
auth() {
return this;
},
verifyIdToken(token) {
return JSON.parse(token);
},
initializeApp(app) {
},
credential: {
cert() {
}
}
}
Since the auth middleware expects to find a user in the test database, I have to configure that as Jest Setup in the beforeAll
hook:
const userOneToken = JSON.stringify({ uid: 'example UID' });
const userOne = {
_id: 'example UID',
// ...
};
beforeAll(async () => {
await User.deleteMany();
await User.save(userOne);
app.use(auth).get('/', (req, res) => res.send());
});
This means that the middleware will always be able to get a UID in return, which can be used to find a test user in the test database.
The test suite itself, after importing my Express Application, is quite simple, with just three tests:
const auth = require('./../../src/middleware/auth');
describe('Express Auth Middleware', () => {
test('Should return 401 with an invalid token', async () => {
await request(app)
.get('/')
.set('Authorization', 'Bearer 123')
.send()
.expect(401);
});
test('Should return 401 without an Authorization Header', async () => {
await request(app)
.get('/')
.send()
.expect(401);
});
test('Should return 200 with a valid token', async () => {
await request(app)
.get('/')
.set('Authorization', `Bearer ${userOneToken}`)
.send()
.expect(200);
});
});
It appears, however, that the tests are leaking memory (apparent by calling with the --detectLeaks
flag). Additionally, it seems Jest is also finding an open handle left behind by the last test. Running the suite with the --detectOpenHandles
flag returns a TCPSERVERWRAP
error on the get
request of the last test.
Potential solutions were proposed in this GitHub issue, but none of them worked for me.
Any help solving this issue would be much appreciated, for all my test suites are leaking memory because they rely on Supertest. Thank you.