Despite my best attempts to correctly write test code to authenticate a request agent in Setup blocks or previous describe/it blocks, any request I make from the agent in subsequent describe/it blocks never completes as 200.
Example code:
const request = require('supertest');
const server = require('../server');
let agent = request.agent(server);
let fakePerson = null;
beforeEach(async (done) => {
fakePerson = await Person.createMock();
agent.post(‘/login’)
.send({
email: ‘test@user.com’,
password: ‘password’
})
.end(function(err, res) {
if (err) throw err;
done();
});
});
describe('GET /users/:id', () => {
it ('renders user profile', () => {
return agent
.get(`/users/${fakePerson.id}`)
.expect(200)
});
});
I thought it might have something to do with how I was forming the async calls syntactically. But after trying different ways of returning the login call in the beforeEach
block using return
, .end()
syntax, even async/await, I've determined (ie given up) that the code must be composed properly. Could it be something else?
Referenced articles/resources:
- How to authenticate Supertest requests with Passport?
- https://medium.com/@juha.a.hytonen/testing-authenticated-requests-with-supertest-325ccf47c2bb
- https://gist.github.com/joaoneto/5152248
- https://medium.com/@bill_broughton/testing-with-authenticated-routes-in-express-6fa9c4c335ca
- https://github.com/visionmedia/supertest/issues/46
Package versions:
- "koa": "^2.4.1"
- "koa-passport": "^4.0.1"
- "passport-json": "^1.2.0"
- "passport-local": "^1.0.0"
- "supertest": "^3.0.0"
- "jest": "^22.1.3"
I took some time stepping through my auth code during test runs and couldn't see any obvious problem. Then I had a thought: what if the request itself was ill formed. Turns out I was right! Inspecting the set-cookie
header in the Supertest response headers I saw:
[ 'koa:sess=eyJwYXNzcG9ydCI6eyJ1c2VyIjoxfSwiX2V4cGlyZSI6MTUyMTIyODg0NTU5OSwiX21heEFnZSI6ODY0MDAwMDB9; path=/; httponly,koa:sess.sig=EEZzgcg3bx8bm_FXRMobrr8_Yts; path=/; httponly' ]
This looked a little suspicious as a single string, which led me to some more googling where I discovered that there were differences in the way that cookie headers were being set for Mocha and Jest users as global state on the Supertest agent instance. See: https://github.com/facebook/jest/issues/3547#issuecomment-302541653. Folks using Mocha had no trouble authenticating, while Jest users did. Turns out there is a bug with Jest globals that is causing the cookie to come in as a single string rather than an array of separate strings for each cookie--which is what Supertest needs to format the request properly.
Here is a workaround based on the code in the question where we correctly parse the buggy Jest string to a scoped variable for the cookie/session data inside the Setup block:
const request = require('supertest');
const server = require('../server');
let agent = request.agent(server);
let fakePerson = null;
let session = null;
beforeEach(async () => {
fakePerson = await Person.createMock();
agent.post(‘/login’)
.send({
email: fakePerson.email,
password: fakePerson.password’
})
.then(res => {
session = res
.headers['set-cookie'][0]
.split(',')
.map(item => item.split(';')[0])
.join(';')
expect(res.status).toEqual(200)
});
});
describe('GET /users/:id', () => {
it ('renders user profile', () => {
return agent
.get(`/users/${fakePerson.id}`)
.set('Cookie', session)
.expect(200)
});
});
I couldn't get internetross's answer to work. Finally after lots of sleuthing, I found this: https://github.com/facebook/jest/issues/3547#issuecomment-397183207.
I had to replace
session = response.headers['set-cookie'][0]
.split(',')
.map(item => item.split(';')[0])
.join('; ')
with
response.headers['set-cookie'][0]
.split(',')
.map(item => item.split(';')[0])
.forEach(c => agent.jar.setCookie(c));
Big sigh.