Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A problem testing Express with Supertest #3547

Closed
zandaqo opened this issue May 11, 2017 · 15 comments
Closed

A problem testing Express with Supertest #3547

zandaqo opened this issue May 11, 2017 · 15 comments

Comments

@zandaqo
Copy link

zandaqo commented May 11, 2017

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
@thymikee
Copy link
Collaborator

Can you provide a simple repro?

@zandaqo
Copy link
Author

zandaqo commented May 11, 2017

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.

@thymikee
Copy link
Collaborator

thymikee commented May 11, 2017

So this seems like issue to be resolved by jsdom, can you describe it there?
Missed that it's --env=node, so definitely unrelated to jsdom.

@zandaqo
Copy link
Author

zandaqo commented May 11, 2017

@thymikee Ok, thank you!

@jakeorr
Copy link

jakeorr commented May 18, 2017

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)

@jakeorr
Copy link

jakeorr commented May 18, 2017

This appears to be related to #2549

@zandaqo
Copy link
Author

zandaqo commented May 18, 2017

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

@jakeorr
Copy link

jakeorr commented May 18, 2017

@zandaqo Thanks for the suggestion.

@thymikee
Copy link
Collaborator

Closing in favour of #2549

@barnash
Copy link

barnash commented Mar 1, 2018

@jakeorr & @zandaqo Did you find a better workaround?
I'm trying to start an express server in the tests with the cookie session library, and since the Set-Cookie header is messed up, it's not working.
Do you know what is the root cause of the issue so I can try to investigate a better workaround?

@Ghnuberath
Copy link

Ghnuberath commented Jun 14, 2018

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

@keepitsimple
Copy link

keepitsimple commented Oct 1, 2018

If your cookie has Expires as a date or you have a comma in the cookie your workaround doesn't work

Set-Cookie: __Host-nonce=4cb862e9-8ff7-4ec0-8c4f-0d24a2f4ec96; Max-Age=7200; Path=/; Expires=Mon, 01 Oct 2018 21:31:13 GMT; HttpOnly; Secure; SameSite=Strict

@Bexanderthebex
Copy link

Do we have a work around on express.js?

@arthurvi
Copy link

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]);

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

8 participants