jest: A problem testing Express with Supertest

We are porting our tests from Mocha to Jest and encountered this weird problem.

Do you want to request a feature or report a bug? bug

What is the current behavior? The following spec tests login and fetching data for an authorized user in Express.js server using Supertest. Before each test suite, it initializes an agent that does the login. In Mocha the test runs without problems. In Jest, however, it successfully logs the user in beforeEach part, but the subsequent requests don’t use the same credentials, hence, get rejected as unauthorized.

const request = require('supertest');
const api = require('../../src/routes/main');

describe('Concepts API', () => {
  let agent;

  beforeEach((done) => {
    agent = request.agent(api);
    agent
      .post('/a/login')
      .send({
        username: 'testerName',
        password: 'testerPassword',
      })
      .expect(200, done);
  });

  describe('GET concepts/names', () => {
    it('returns the list of names of the fist 100 concepts', (done) => {
      agent
        .get('/concepts/names')
        .set('X-Requested-With', 'XMLHttpRequest')
        .expect(200)
        .end((e, r) => {
          expect(r.body.concepts).toBeDefined();
          done();
        });
    });
  });
});

What is the expected behavior? Test should pass.

Please provide your exact Jest configuration and mention your Jest, node, yarn/npm version and operating system. Jest 20.0.0 Supertest 3.0.0 Express.js 4.15.2

The command used to run Jest (there are no other configs):

jest concepts.spec.js --env=node --runInBand

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 1
  • Comments: 15

Commits related to this issue

Most upvoted comments

@jakeorr We seem to use the same middlewares. In case you need a temporary workaround, we went with manually parsing cookies after login and then supplying them in following requests. That is:

  const agent = request.agent(api);
  let cookie;

  beforeAll(() => agent
        .post('/login')
        .send({
          username: testerName,
          password: testerPassword,
        })
        .expect(200)
        .then((res) => {
          const cookies = res.headers['set-cookie'][0].split(',').map(item => item.split(';')[0]);
          cookie = cookies.join(';');
         }));

  describe('GET logout', () => {
    it('logs user out', () => agent
        .get('/logout')
        .set('Cookie', cookie)
        .expect(302));
  });

Hi, I’m having the same issue. Here’s a simple repro:

const express = require('express');
const cookieSession = require('cookie-session');
const app = express();

app.use(
  cookieSession({
    secret: 'asdfasdfasdfasdf',
    maxAge: 60 * 60 * 24 * 1000,
  })
);

app.get('/', (req, res) => {
  req.session.foo = 'bar';
  res.send('ok');
});

describe('tests', () => {
  const supertest = require('supertest');
  const agent = supertest.agent(app);

  it('first request', done => {
    agent.get('/').expect(200).end((err, res) => {
      if (err) return done(err);
      console.log('res.headers', res.headers);
      done();
    });
  });

  it('second request', done => {
    agent.get('/').expect(200).end((err, res) => {
      if (err) return done(err);
      console.log('res.headers', res.headers);
      done();
    });
  });
});

I’m doing two requests to test for the same cookie-session being used for both. I expect to see ‘set-cookie’ headers in the first response, and not in the second. Also, the log of req.session for the second request should contain session data from the first. It doesn’t work in Jest. Here’s the output:

yarn test v0.23.4
$ jest
 PASS  test/index.spec.js
  tests
    ✓ first request (31ms)
    ✓ second request (6ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.747s
Ran all test suites.
  console.log test/index.spec.js:14
    req.session Session {}

  console.log test/index.spec.js:25
    res.headers { 'x-powered-by': 'Express',
      'content-type': 'text/html; charset=utf-8',
      'content-length': '2',
      etag: 'W/"2-eoX0dku9ba8cNUXvu/DyeabcC+s"',
      'set-cookie': [ 'session=eyJmb28iOiJiYXIifQ==; path=/; expires=Fri, 19 May 2017 21:03:40 GMT; httponly,session.sig=PQb6IC6Qn-hfN5ISsYkbQMrMQ5Y; path=/; expires=Fri, 19 May 2017 21:03:40 GMT; httponly' ],
      date: 'Thu, 18 May 2017 21:03:40 GMT',
      connection: 'close' }

  console.log test/index.spec.js:14
    req.session Session {}

  console.log test/index.spec.js:33
    res.headers { 'x-powered-by': 'Express',
      'content-type': 'text/html; charset=utf-8',
      'content-length': '2',
      etag: 'W/"2-eoX0dku9ba8cNUXvu/DyeabcC+s"',
      'set-cookie': [ 'session=eyJmb28iOiJiYXIifQ==; path=/; expires=Fri, 19 May 2017 21:03:40 GMT; httponly,session.sig=PQb6IC6Qn-hfN5ISsYkbQMrMQ5Y; path=/; expires=Fri, 19 May 2017 21:03:40 GMT; httponly' ],
      date: 'Thu, 18 May 2017 21:03:40 GMT',
      connection: 'close' }

✨  Done in 2.56s.

When I run this spec with mocha, it works as expected:

./node_modules/mocha/bin/_mocha


  tests
req.session Session {}
res.headers { 'x-powered-by': 'Express',
  'content-type': 'text/html; charset=utf-8',
  'content-length': '2',
  etag: 'W/"2-eoX0dku9ba8cNUXvu/DyeabcC+s"',
  'set-cookie':
   [ 'session=eyJmb28iOiJiYXIifQ==; path=/; expires=Fri, 19 May 2017 21:03:48 GMT; httponly',
     'session.sig=PQb6IC6Qn-hfN5ISsYkbQMrMQ5Y; path=/; expires=Fri, 19 May 2017 21:03:48 GMT; httponly' ],
  date: 'Thu, 18 May 2017 21:03:48 GMT',
  connection: 'close' }
    ✓ first request
req.session Session { foo: 'bar' }
res.headers { 'x-powered-by': 'Express',
  'content-type': 'text/html; charset=utf-8',
  'content-length': '2',
  etag: 'W/"2-eoX0dku9ba8cNUXvu/DyeabcC+s"',
  date: 'Thu, 18 May 2017 21:03:48 GMT',
  connection: 'close' }
    ✓ second request


  2 passing (46ms)

A bit of further debugging shows that Jest and Mocha set cookies differently. After login, we return cookies with session info in Set-Cookie header. Jest sets all values as a single string,

res.headers['set-cookie'] = ['session=eyJwYXNzcG9ydCI6ey=; path=/; httponly,session.sig=ojBbO_f2GgMPPJrFA; path=/; httponly']

Whereas in Mocha we have two strings:

res.headers['set-cookie'] = ['session=eyJwYXNzcG9ydCI6ey=; path=/; httponly', 'session.sig=ojBbO_f2GgMPPJrFA; path=/; httponly']

The subsequent requests use different “Cookie” header in Jest and Mocha. Jest has just the first key-value pair:

req.headers['cookie'] = session=eyJwYXNzcG9ydCI6ey=

Mocha has both:

req.headers['cookie'] = session=eyJwYXNzcG9ydCI6ey=;session.sig=ojBbO_f2GgMPPw6lsj4r7lUJrFA

@thymikee I don’t see an easy way to share our stack, but I believe the culprit is in this cookie setting procedure.

If your cookie has Expires as a date, here is a workaround (@keepitsimple):

  const cookies = response.headers['set-cookie'][0]
    .split(/,(?=\S)/)
    .map((item) => item.split(';')[0]);

Mysteriously, this workaround worked for me locally but not on travis-ci (despite identical node versions). I had to modify it as follows for consistent behaviour across both environments:

const res = await agent.post('/auth/local'); // ... etc
res.headers['set-cookie'][0]
        .split(',')
        .map(item => item.split(';')[0])
        .forEach(c => agent.jar.setCookie(c));