#Sharing the same app across e2e tests?

16 messages · Page 1 of 1 (latest)

high pagoda
#

According to the docs, for each e2e file, a separate app instance is started. Because of that, in a project we're working with the devs added all e2e tests in 1 huge file instantiating the app in a beforeAll() block.

Do you have your own solution where you start the app just once and that's used for all e2e tests, which are located in separate files instead?
I'd like to split this huge file into separate files but not start several app instances

hushed tartan
#

I'm working on a bit of an approach for this, where you have a single .e2e-spec.ts file thast has that beforeAll hook, but all of the testss are essentlayy the callback methods for describe blocvks and are written in other files

Still a work in progress though

high pagoda
#

that's what we've done, but still, now the file is 2038 lines long 😄

hushed tartan
#

Are you moving the describe callbacks to their own files?

high pagoda
#

it's going like

describe('something something', () => {
it('should do xyz', () => {
return myTestScenario(app);
});
// more it() blocks
});

// more describe blocks with it blocks inside of them, etc...

#

test scenarios in other files

wanton smelt
#

If you don't need to reach into the application to override stuff or get some providers and can do with starting the app on a real port and call real endpoints in the tests, you can use jest's globalSetup and globalTeardown to spin up a single instance of the application accross all test files

high pagoda
#

I've tried doing that today but it broke trying to solve the import paths. I'll take another look

high pagoda
#

globalSetup on jest config works by setting app to global. I just had to change all imports from absolute to relative so it would find all paths

#

It works for most tests, however for the tests where we use app.get(), the context is lost

#

while it was working if the app instantiation happens inside of a beforeAll() block

wanton smelt
#

You can only reach the global from globalSetup in globalTeardown. The context is not shared with the test files, they run in a completely different Node process. That's what I wrote earlier - you can only use this technique if you don't need to reference the application in your e2e tests.

high pagoda
#

so unless someone else has a better idea, it's unfortunately all in the same file, importing scenarios from other files inside of it() blocks

hushed tartan
#

This seems to work correctly in vitest:
main.ts

import { NestFactory } from '@nestjs/core';
import { OgmaService } from '@ogma/nestjs-module';
import { RootModule } from '@unteris/server/root';
import { request, spec } from 'pactum';
import { describe, beforeAll } from 'vitest';
import { csrfTest } from './tests/csrf';

describe('Unteris E2E test suite', () => {
  beforeAll(async () => {
    const app = await NestFactory.create(RootModule, { bufferLogs: true });
    app.useLogger(app.get(OgmaService));
    await app.listen(0);
    const reqURL = await app.getUrl();
    request.setBaseUrl(reqURL.replace('[::1]', 'localhost'));
    return async () => {
      await app.close();
    };
  });
  csrfTest();
});

tests/csrf.ts'

import { parse } from 'lightcookie';
import { spec } from 'pactum';
import { describe, expect, test } from 'vitest';

export const csrfTest = () => {
  return describe('CSRF Tests', () => {
    test('CSRF Testing', async () => {
      await spec()
        .get('/csrf')
        .expectStatus(200)
        .expectJsonLike({ csrfToken: /\w+/ })
        .expect(({ res }) => {
          const cookies = res.headers['set-cookie'];
          if (!cookies || cookies.length === 0) {
            throw new Error('Received no cookies from the server');
          }
          cookies.forEach((cookie) => {
            const parsed = parse(cookie);
            expect(
              ['sessionId', 'refreshId'].some((cookieName) => {
                return cookieName in parsed;
              })
            );
          });
        })
        .stores((_req, res) => {
          const { csrfToken } = res.body;
          const cookies = res.headers['set-cookie'];
          if (!cookies || cookies.length === 0) {
            throw new Error('Received no cookies from the server');
          }
          const sessionId = cookies
            .map((c) => parse(c))
            .find((cookie) => 'sessionId' in cookie)?.sessionId;
          const refreshId = cookies
            .map((c) => parse(c))
            .find((cookie) => 'refreshId' in cookie)?.refreshId;
          return {
            csrfToken,
            sessionId,
            refreshId,
          };
        })
        .toss();
      await spec()
        .post('/csrf/verify')
        .withHeaders('X-UNTERIS-CSRF-PROTECTION', '$S{csrfToken}')
        .withCookies('sessionId', '$S{sessionId}')
        .expectStatus(201)
        .expectJson({ success: true })
        .toss();
    });
  });
};

Now, I am using pactum so that I don't need direct access to the app instance, but I would bet that it's possible to pass that instance as necessary

plush eagle
hushed tartan
#

Not in my case. My main.ts is the server-e2e entry point