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.

referencespostgresrules.md

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

PostgreSQL E2E Testing Rules

Core Rules

Rule Requirement
Port Use different port from dev (e.g., 5433)
Cleanup TRUNCATE with CASCADE in beforeEach
Cleanup order Children before parents (FK constraints)
Sequences Reset after TRUNCATE if predictable IDs needed
Connections Single connection for tests (max: 1)

Cleanup Rules

TRUNCATE with CASCADE (Recommended)

beforeEach(async () => {
  await dataSource.query('SET session_replication_role = replica');
  await dataSource.query('TRUNCATE users, orders, products CASCADE');
  await dataSource.query('SET session_replication_role = DEFAULT');
});

Manual Order (Without CASCADE)

beforeEach(async () => {
  // Children first
  await orderItemRepository.delete({});
  await orderRepository.delete({});
  // Parents last
  await userRepository.delete({});
  await productRepository.delete({});
});

Sequence Reset

afterEach(async () => {
  await dataSource.query(`ALTER SEQUENCE users_id_seq RESTART WITH 1`);
  await dataSource.query(`ALTER SEQUENCE orders_id_seq RESTART WITH 1`);
});

Connection Rules

// TypeORM DataSource config for tests
{
  type: 'postgres',
  url: process.env.DATABASE_URL,
  extra: {
    max: 1,  // Single connection for tests
  },
  synchronize: false,  // Don't auto-sync in tests
}

Transaction Rules

Transaction Rollback Pattern

describe('with transaction rollback', () => {
  let queryRunner: QueryRunner;

  beforeEach(async () => {
    queryRunner = dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();
  });

  afterEach(async () => {
    await queryRunner.rollbackTransaction();
    await queryRunner.release();
  });

  it('should not persist data', async () => {
    // Data created here will be rolled back
  });
});

Assertion Rules

Assert Record Exists

// ✅ GOOD: Assert specific values
await helper.assertRecordExists(User, { email: 'test@example.com' }, {
  name: 'Test User',
  status: 'active',
});

// ❌ BAD: Only check existence
const user = await userRepository.findOne({ where: { email: 'test@example.com' } });
expect(user).toBeDefined();

Assert Record Count

await helper.assertRecordCount(Order, { userId: user.id }, 3);

Assert Record Not Exists

await helper.assertRecordNotExists(User, { email: 'deleted@example.com' });

Query Rules

Use Repository Methods

// ✅ GOOD: Repository methods
const users = await userRepository.find({
  where: { status: 'active' },
  order: { createdAt: 'DESC' },
  take: 10,
});

// ❌ BAD: Raw SQL (unless necessary)
const users = await dataSource.query('SELECT * FROM users WHERE status = $1', ['active']);

Use QueryBuilder for Complex Queries

const result = await userRepository
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.orders', 'order')
  .where('user.status = :status', { status: 'active' })
  .andWhere('order.total > :total', { total: 100 })
  .getMany();

Performance Rules

Batch Operations

// ✅ GOOD: Batch insert
await userRepository.save(users);

// ❌ BAD: Individual inserts
for (const user of users) {
  await userRepository.save(user);
}

Index Management for Large Data

// Disable triggers during bulk load
beforeAll(async () => {
  await dataSource.query(`ALTER TABLE users DISABLE TRIGGER ALL`);
});

afterAll(async () => {
  await dataSource.query(`ALTER TABLE users ENABLE TRIGGER ALL`);
  await dataSource.query(`REINDEX TABLE users`);
});

Error Handling Rules

Foreign Key Constraint Errors

it('should return error when deleting user with orders', async () => {
  // GIVEN: User with orders
  const user = await userRepository.save({ email: 'test@example.com' });
  await orderRepository.save({ userId: user.id, total: 100 });

  // WHEN: Attempting to delete user
  const response = await request(httpServer)
    .delete(`/api/v1/users/${user.id}`)
    .expect(409);

  // THEN: Conflict error
  expect(response.body.code).toBe('USER_HAS_ORDERS');
});

Unique Constraint Errors

it('should return error for duplicate email', async () => {
  // GIVEN: Existing user
  await userRepository.save({ email: 'existing@example.com', name: 'Existing' });

  // WHEN: Creating user with same email
  const response = await request(httpServer)
    .post('/api/v1/users')
    .send({ email: 'existing@example.com', name: 'New' })
    .expect(409);

  // THEN: Duplicate error
  expect(response.body.code).toBe('EMAIL_ALREADY_EXISTS');
});

Checklist

Setup:

  • Different port from dev (e.g., 5433)
  • Performance settings enabled (fsync=off, etc.)
  • Single connection pool (max: 1)

Cleanup:

  • TRUNCATE with CASCADE in beforeEach
  • Sequence reset if needed
  • Use session_replication_role = replica for CASCADE

Assertions:

  • Assert specific values, not just existence
  • Verify all expected fields
  • Check record counts where applicable

Performance:

  • Use batch operations for seeding
  • Avoid N+1 queries in test setup

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.