All skills
bmad-labs avatar

/typescript-e2e-testing

@3bcc7a6
by bmad-labsbmad-labs/skills15 stars
4

E2E and integration testing for TypeScript/NestJS projects using Jest, supertest, and real infrastructure via Docker (Kafka, PostgreSQL, MongoDB, Redis) with the Given-When-Then pattern. Use whenever the user is working on `.e2e-spec.ts` files or anything under `test/e2e/`, or asks to set up, write, review, run, debug, or optimize E2E or integration tests — including flaky tests, docker-compose for tests, Kafka/Redpanda consumers, test isolation, or GWT compliance.

Use this Skill: https://skilld.dev/gh/bmad-labs/skills/typescript-e2e-testing

This session only. Nothing lands on disk.

referencespostgrestest-helper.md

≈1.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

PostgreSQL Test Helper

Complete Implementation

// test/helpers/postgres.helper.ts
import { DataSource, EntityTarget, FindOptionsWhere, DeepPartial } from 'typeorm';

export class PostgresTestHelper {
  constructor(private dataSource: DataSource) {}

  /**
   * Clear all tables with CASCADE to handle FK constraints.
   */
  async clearTables(tables: string[]): Promise<void> {
    await this.dataSource.query('SET session_replication_role = replica');
    for (const table of tables) {
      await this.dataSource.query(`TRUNCATE TABLE "${table}" CASCADE`);
    }
    await this.dataSource.query('SET session_replication_role = DEFAULT');
  }

  /**
   * Clear all tables in the database (except migrations).
   */
  async clearAllTables(): Promise<void> {
    const tables = await this.dataSource.query(`
      SELECT tablename FROM pg_tables
      WHERE schemaname = 'public' AND tablename != 'migrations'
    `);
    const tableNames = tables.map(t => t.tablename);
    await this.clearTables(tableNames);
  }

  /**
   * Assert record exists with expected values.
   */
  async assertRecordExists<T>(
    entity: EntityTarget<T>,
    where: FindOptionsWhere<T>,
    expected?: Partial<T>
  ): Promise<T> {
    const repository = this.dataSource.getRepository(entity);
    const record = await repository.findOne({ where });

    expect(record).toBeDefined();
    expect(record).not.toBeNull();

    if (expected) {
      expect(record).toMatchObject(expected);
    }
    return record!;
  }

  /**
   * Assert record does not exist.
   */
  async assertRecordNotExists<T>(
    entity: EntityTarget<T>,
    where: FindOptionsWhere<T>
  ): Promise<void> {
    const repository = this.dataSource.getRepository(entity);
    const record = await repository.findOne({ where });
    expect(record).toBeNull();
  }

  /**
   * Assert count of records.
   */
  async assertRecordCount<T>(
    entity: EntityTarget<T>,
    where: FindOptionsWhere<T>,
    expectedCount: number
  ): Promise<void> {
    const repository = this.dataSource.getRepository(entity);
    const count = await repository.count({ where });
    expect(count).toBe(expectedCount);
  }

  /**
   * Seed data for testing.
   */
  async seedData<T>(entity: EntityTarget<T>, data: Partial<T>[]): Promise<T[]> {
    const repository = this.dataSource.getRepository(entity);
    return repository.save(data as DeepPartial<T>[]);
  }

  /**
   * Reset sequences for predictable IDs.
   */
  async resetSequences(): Promise<void> {
    const sequences = await this.dataSource.query(`
      SELECT sequence_name FROM information_schema.sequences
      WHERE sequence_schema = 'public'
    `);
    for (const { sequence_name } of sequences) {
      await this.dataSource.query(`ALTER SEQUENCE "${sequence_name}" RESTART WITH 1`);
    }
  }

  /**
   * Execute raw SQL query.
   */
  async executeQuery<T = any>(sql: string, parameters?: any[]): Promise<T> {
    return this.dataSource.query(sql, parameters);
  }

  /**
   * Begin transaction for test isolation.
   */
  async beginTransaction(): Promise<void> {
    await this.dataSource.query('BEGIN');
  }

  /**
   * Rollback transaction.
   */
  async rollbackTransaction(): Promise<void> {
    await this.dataSource.query('ROLLBACK');
  }

  /**
   * Get repository for entity.
   */
  getRepository<T>(entity: EntityTarget<T>) {
    return this.dataSource.getRepository(entity);
  }
}

Usage Example

describe('User API E2E', () => {
  let helper: PostgresTestHelper;
  let userRepository: Repository<User>;

  beforeAll(async () => {
    const setup = await createTestApp([UserModule]);
    helper = new PostgresTestHelper(setup.app.get(DataSource));
    userRepository = helper.getRepository(User);
  });

  beforeEach(async () => {
    await helper.clearTables(['users', 'orders', 'products']);
    await helper.resetSequences();
  });

  it('should create user', async () => {
    // GIVEN: No users exist

    // WHEN: Creating user
    const response = await request(httpServer)
      .post('/users')
      .send({ email: 'test@example.com', name: 'Test' })
      .expect(201);

    // THEN: User persisted correctly
    await helper.assertRecordExists(User, { email: 'test@example.com' }, {
      name: 'Test',
      status: 'active',
    });
  });

  it('should return users with pagination', async () => {
    // GIVEN: Multiple users exist
    await helper.seedData(User, [
      { email: 'user1@test.com', name: 'User 1' },
      { email: 'user2@test.com', name: 'User 2' },
      { email: 'user3@test.com', name: 'User 3' },
    ]);

    // WHEN: Fetching with pagination
    const response = await request(httpServer)
      .get('/users')
      .query({ page: 1, limit: 2 })
      .expect(200);

    // THEN: Paginated results returned
    expect(response.body.data).toHaveLength(2);
    expect(response.body.meta.total).toBe(3);
  });
});

API Reference

Method Description
clearTables(tables) TRUNCATE specified tables with CASCADE
clearAllTables() TRUNCATE all tables except migrations
assertRecordExists(entity, where, expected) Assert record exists with values
assertRecordNotExists(entity, where) Assert record does not exist
assertRecordCount(entity, where, count) Assert record count
seedData(entity, data) Insert test data
resetSequences() Reset all sequences to 1
executeQuery(sql, params) Execute raw SQL
beginTransaction() Start transaction
rollbackTransaction() Rollback transaction
getRepository(entity) Get TypeORM repository

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides comprehensive workflows and reference materials for E2E testing in TypeScript/NestJS projects. It uses standard development tools like Jest, Docker, and shell commands. No malicious patterns or security risks were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    30/103 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 3bcc7a6. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated 5 months ago
  • TypeScript
  • jest
  • nestjs
  • e2e-testing
  • supertest
  • docker
  • kafka
  • postgres
  • mongodb
  • redis
  • integration-testing
  • gwt-pattern

README badge

README badge for bmad-labs/skills

Writes E2E and integration tests for NestJS projects using Jest and supertest against real Docker infrastructure (Kafka, PostgreSQL, MongoDB, Redis) following the Given-When-Then pattern. Covers test setup, writing, review, debugging, and optimization workflows with technology-specific helpers and isolation strategies for each service.

Generated from the current SKILL.md.

Does this skill work with NestJS only?
The skill is built for NestJS projects using Jest and supertest, but the patterns and workflows apply to any TypeScript backend with HTTP endpoints. Technology-specific helpers exist for Kafka, PostgreSQL, MongoDB, and Redis.
Do I need Docker running to use this skill?
Yes. The skill assumes real infrastructure via Docker (Kafka, PostgreSQL, MongoDB, Redis). Tests execute against actual services, not mocks.
What if my E2E tests are flaky or failing?
The Debugging E2E Test workflow provides a systematic protocol: fix one test at a time using isolated test runs, verify with 3-5 consecutive runs before moving to the next failure, then run the full suite only once all tests pass individually.
Does this skill cover GraphQL or gRPC testing?
Yes. The API testing reference includes examples for REST, GraphQL, and gRPC, plus external API mocking with MSW and Nock.
What is the Given-When-Then pattern and is it mandatory?
GWT is the mandatory test structure: Given describes setup state, When describes the action under test, Then describes expected outcomes. All E2E tests in this skill must follow this pattern.

Generated from the current SKILL.md. These answers refresh after source changes.