request.cookies is undefined when using Supertest
Asked Answered
H

4

13

I'm passing my authentication token via an HTTP-Only cookie in my NestJS API.

As such, when writing some E2E tests for my Auth endpoints, I'm having an issue with cookies not being where I expect them.

Here's my pared-down test code:

describe('auth/logout', () => {
  it('should log out a user', async (done) => {
    // ... code to create user account

    const loginResponse: Response = await request(app.getHttpServer())
                                              .post('/auth/login')
                                              .send({ username: newUser.email, password });

    // get cookie manually from response.headers['set-cookie']
    const cookie = getCookieFromHeaders(loginResponse);

    // Log out the new user
    const logoutResponse: Response = await request(app.getHttpServer())
                                            .get('/auth/logout')
                                            .set('Cookie', [cookie]);

  });
});

In my JWT Strategy, I'm using a custom cookie parser. The problem I'm having is that request.cookies is always undefined when it gets to the parser. However, the cookie will be present in request.headers.

I'm following the manual cookie example from this Medium article: https://medium.com/@juha.a.hytonen/testing-authenticated-requests-with-supertest-325ccf47c2bb, and there don't appear to be any other methods available on the request object to set cookies.

If I test the same functionality from Postman, everything works as expected. What am I doing wrong?

Hygroscopic answered 1/2, 2020 at 17:35 Comment(2)
Are you using Fastify? If so you need to register FastifyCookies still when creating the testing application.Annexation
No, I'm not suing FastifyHygroscopic
J
11

I know this is an old thread but...

I also had req.cookies undefined, but for a different reason.

I'm testing my router independently, not the top level app. So I bootstrap the app in beforeEach and add the route to test.

I was getting req.cookies undefined because express 4 requires the cookieParser middleware to be present to parse the cookies from the headers.

E.g.


const express           = require('express');
const bodyParser        = require('body-parser');
const cookieParser      = require('cookie-parser');
const request           = require('supertest');

const {router}  = require('./index');

describe('router', () => {
    let app;    
    
    beforeAll(() => {        
        app  = express();
        app.use(bodyParser.json());
        app.use(bodyParser.urlencoded({ extended: true }));
        app.use(cookieParser());
        app.use('/', router);
    });

    beforeEach(() => jest.clearAllMocks());

    it('GET to /', async () => {
        const jwt = 'qwerty-1234567890';
        
        const resp = await request(app)
            .get('/')
            .set('Cookie', `jwt=${jwt};`)
            .set('Content-Type', 'application/json')
            .send({});        
    });
    
});

Testing this way allows me to unit test a router in isolation of the app. The req.cookies turn up as expected.

Jihad answered 8/7, 2020 at 16:23 Comment(1)
Sorry for the slow reply, I've been out sick for a while. Thank you very much for the post, I'll have to give that a try.Hygroscopic
P
4

Late but I hope I can help you. The problem is in the initialization of the app object. Probably in your main.ts file you have some middlewares configured as they are: CORS and queryParse. You must also put them in your tests when you create the app.

const moduleFixture: TestingModule = await Test.createTestingModule({ 
  imports: [AppModule]
}).compile();

const app = moduleFixture.createNestApplication();

// Add CORS
app.enableCors({ credentials: true, origin: ['http://localhost:4200'] });

// Add cookie parser
app.use(cookieParser());

await app.init();
Prostration answered 20/5, 2022 at 20:31 Comment(0)
P
0

As per the article you're following, the code at https://medium.com/@juha.a.hytonen/testing-authenticated-requests-with-supertest-325ccf47c2bb :
1) has the 'cookie' value in .set('cookie', cookie) in lowercase and in your code it's in Pascal case ==> Have you tried with lowercase in your code instead ?
2) the cookie value assigned to the 'cookie' header is not an array, whereas in your code you're assigning an array ==> Have you tried with a non array value ?

So to resume, can you try with the following code:

describe('auth/logout', () => {
  it('should log out a user', async (done) => {
    // ... code to create user account

    const loginResponse: Response = await request(app.getHttpServer())
                                              .post('/auth/login')
                                              .send({ username: newUser.email, password });

    // get cookie manually from response.headers['set-cookie']
    const cookie = getCookieFromHeaders(loginResponse);

    // Log out the new user
    const logoutResponse: Response = await request(app.getHttpServer())
                                            .get('/auth/logout')
                                            .set('cookie', cookie) // <== here goes the diff
                                            .expect(200, done);

  });
});

Let us know if that helps :)

Pico answered 3/2, 2020 at 10:3 Comment(1)
Hello. My apologies for the very late response. Yes, I did previously try lowercasing the value and using a non-array value, but the cookie still ended up as a header instead of in the request.cookies collection. To work around this, I added an extractor to pull the token from the header as a fallback in my Passport strategy.Hygroscopic
S
0

You need to import cookie-parser in your test file to ensure the test app can use cookies. In my case, I use it like this:

............another imported files
import * as cookieParser from 'cookie-parser';

describe('AuthController', () => {
  let app: INestApplication;
  let logger: Logger;
  let testService: TestService;

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule, TestModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    logger = app.get(WINSTON_MODULE_PROVIDER);
    testService = app.get(TestService);

    // Use cookie parser in the test app
    app.use(cookieParser());
    await app.init();
  });

  describe('GET /api/v1/auth/current', () => {
    beforeEach(async () => {
      await testService.deleteUser();
      await testService.createUser();
      await testService.verifyEmail();
    });

    afterEach(async () => {
      await testService.deleteUser();
    });

    it('should get the current user', async () => {
      const tokens = await testService.login(app);
      const response = await request(app.getHttpServer())
        .get('/api/v1/auth/current')
        .set('Cookie', [`accesstoken=${tokens.accessToken}`]);

      logger.info(response.body);
      expect(response.status).toBe(200);
      expect(response.body.data.id).toBeDefined();
      expect(response.body.data.email).toBe('[email protected]');
      expect(response.body.data.username).toBe('example');
    });
  });
});

This ensures cookies work correctly during your test cases, don't forget to console log every data in and out either when request in and out so you can see where the data error is happening. Hope this can help.

Spessartite answered 17/8 at 16:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.