node.jsjestjssupertestkoa2

Unable to send authenticated request in tests using Jest, Supertest, Passport, Koa2


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:

Package versions:


Solution

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