Today I Learned

Faster E2E tests & stable DB setup in NestJS

Link to earlier post on E2E tests in NestJS

The following setup allowed to cut down the duration of E2E tests by 2/3 (from 356s to 111s). The app uses TypeORM.

A single app instance for the whole E2E run.

File: test/utils/create-testing-module.ts

// Single app instance
let app: INestApplication

export async function createTestingModule() {
  const moduleBuilder = Test.createTestingModule({
    imports: [AppModule],
  })

  const module = await moduleBuilder.compile()

  app = module.createNestApplication(undefined, {
    logger: false,
  })

  await app.init()
}

export async function closeTestingModule() {
  await getConnection().dropDatabase()

  if (app) await app.close()
}

export function getTestingModule() {
  if (!app) throw 'No app was initialized!!!'

  return app
}

Functions to drop & clean up the DB:

File test/utils/clean-up-db.ts - a collection of functions to drop/clean up the DB:

const tableNames = [
  'contact',
  'user',
]

export async function cleanUpDb() {
  const connection = getConnection()

  for (const tableName of tableNames) {
    await connection.query(`DELETE FROM ${tableName};`)
  }
}

export async function dropTables() {
  const connection = await createConnection({
    type: 'mysql',
    username: process.env.TYPEORM_USERNAME,
    password: process.env.TYPEORM_PASSWORD,
    database: process.env.TYPEORM_DATABASE,
  })

  await connection.query('SET FOREIGN_KEY_CHECKS=0;')
  for (const tableName of tableNames) {
    await connection.query(`DROP TABLE IF EXISTS ${tableName};`)
  }

  await connection.close()
}

Hooks to bootstrap the app and clean up the DB between executions:

File jest.e2e-setup.ts - to be included to the jest configuration:

beforeAll(async () => {
  await dropTables()
  await createTestingModule()
})

afterAll(async () => {
  await closeTestingModule()
})

beforeEach(async () => {
  await cleanUpDb()
})

SQL migrations in TypeORM before a test suite

The setting is via migrationsRun. In this way, TypeORM runs SQL migrations.

App instance for test cases

describe('TagResolver (E2E)', () => {
  let app: INestApplication
  let userModel: UserModel

  beforeEach(async () => {
    app = getTestingModule()

    tagModel = app.get<TagModel>(TagModel)
  })

  it('verifies the app', () => {
    // ...
  })
})