Search code examples
typescriptjestjssupertest

How to prevent redundant code in my jest e2e testing?


I am just learning to build e2e tests with Jest and Supertest.

Test background

Some APIs are protected by Api Keys so I need to create them on the test before running these APIs. The problem is that I have not found a way to create the API Key only once and running the same Key for all my tests. So, the result is a very verbose test that runs the same code over and over to re-create accounts and keys.

My Code:

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import * as chai from 'chai';
import { AppModule } from '../src/app.module';
import { ProductEvent } from 'src/events/dto/product-event.dto';
import { Account } from 'src/accounts/entities/account.entity';
import { randBrand } from '@ngneat/falso';
import { ApiKey } from 'src/accounts/entities/apikey.entity';
import { CreateApiKeyData } from 'src/accounts/dto/create-api-key-data.dto';
import { CreateAccountData } from 'src/accounts/dto/create-account-data';

describe('Accounts (e2e)', () => {
  let app: INestApplication;

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

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  afterAll(async () => {
    await app.close();
  });

  describe('Accounts and Keys (e2e)', () => {
    it('creates an account and key (POST)', () => {
      const accountName = randBrand();
      return request(app.getHttpServer())
        .post('/accounts')
        .send({ name: accountName })
        .expect(201)
        .then(({ body }) => {
          expect(body.name).toEqual(accountName);
          const createdId = body._id;
          return request(app.getHttpServer())
            .post('/apikeys')
            .send({ accountId: body._id })
            .expect(201)
            .then(({ body }) => {
              expect(body.accountId).toEqual(createdId);
            });
        });
    });
  });

  describe('Users (e2e)', () => {
    it('patches an user (PATCH)', () => {
      const accountName = randBrand();
      return request(app.getHttpServer())
        .post('/accounts')
        .send({ name: accountName })
        .expect(201)
        .then(({ body }) => {
          expect(body.name).toEqual(accountName);
          const createdAccountId = body._id;
          return request(app.getHttpServer())
            .post('/apikeys')
            .send({ accountId: body._id })
            .expect(200)
            .then(({ body }) => {
              expect(body.accountId).toEqual(createdAccountId);
              const createdKey = body.apiKey;
              return request(app.getHttpServer())
                .patch('/users/1')
                .send({ name: 'SomeUser', properties: { age: '20' } })
                .set('Accept', 'application/json')
                .set({ 'x-api-Key': createdKey, Accept: 'application/json' })
                .expect(201)
                .then(({ body }) => {
                  expect(body.accountId).toEqual(createdAccountId);
                  expect(body.name).toEqual('SomeUser');
                });
            });
        });
    });
  });

  describe('Events (e2e)', () => {
    it('creates an event (POST)', () => {
      const accountName = randBrand();
      return request(app.getHttpServer())
        .post('/accounts')
        .send({ name: accountName })
        .expect(201)
        .then(({ body }) => {
          expect(body.name).toEqual(accountName);
          const createdAccountId = body._id;
          return request(app.getHttpServer())
            .post('/apikeys')
            .send({ accountId: body._id })
            .expect(201)
            .then(({ body }) => {
              expect(body.accountId).toEqual(createdAccountId);
              const createdKey = body.apiKey;
              return request(app.getHttpServer())
                .post('/events')
                .send({
                  name: 'login',
                  userId: '1',
                  groupId: '2',
                  properties: { client: 'mobile' },
                })
                .set('Accept', 'application/json')
                .set({ 'x-api-Key': createdKey, Accept: 'application/json' })
                .expect(201)
                .then(({ body }) => {
                  expect(body.accountId).toEqual(createdAccountId);
                  expect(body.name).toEqual('login');
                });
            });
        });
    });
  });
});

What I am trying to do

There is a couple ways I could go around this redundancy, I think.

Persist the apiKey generated in the first test and re-use it on the following tests. But, so far, I have not found a way to share a variable state between tests.

Or...

Isolate the account/key creation process in a function or something that I could just re-use on the other tests without all this verbosity.

Can you please help me figure out how to either or both?

Sorry for the basic question. I am just learning things here. Searched Google extensively and couldn't find a solution.


Solution

  • To share information between tests you can use beforeAll() to get call the methods needed for set up. Like this:

    describe('e2e test', () => {
      let apiKey;
    
      beforeAll(async () => {
        apiKey = await methodThatRetrievesTheApiKey();
    
      });
      it('test that uses the apiKey', () => {
    
      });
      it('another test that uses the apiKey', () => {
    
      });
    });
    

    Notice how can then write more than one test within the same "describe" section while using the same info which will be set before running any test.

    You can also use the methods beforeEach, AfterEach or AfterAll as needed.

    For more info checkout: https://jestjs.io/docs/setup-teardown