AgentSkillsCN

pgpm-testing

借助 pgsql-test,以隔离的数据库运行 PostgreSQL 集成测试。当您被要求“运行数据库测试”、“搭建测试数据库”、“编写集成测试”、“测试 PGPM 模块”,或在实施需要 PostgreSQL 支持的测试时,此方法将助您事半功倍。

SKILL.md
--- frontmatter
name: pgpm-testing
description: Run PostgreSQL integration tests with isolated databases using pgsql-test. Use when asked to "run database tests", "set up test database", "write integration tests", "test PGPM modules", or when implementing tests that need PostgreSQL.
compatibility: Node.js 18+, Jest or compatible test runner, pgsql-test package, PostgreSQL running locally
metadata:
  author: constructive-io
  version: "1.0.0"

PGPM Testing

Run PostgreSQL integration tests with isolated databases using the pgsql-test package.

Testing Framework Standard

IMPORTANT: Constructive projects use Jest as the standard testing framework. Do NOT use vitest, mocha, or other test runners unless explicitly approved. Jest provides:

  • Consistent testing experience across all packages
  • Built-in mocking and assertion libraries
  • Snapshot testing support
  • Parallel test execution

When to Apply

Use this skill when:

  • Writing integration tests that need a database
  • Testing PGPM modules or migrations
  • Setting up isolated test databases
  • Seeding test data from SQL files or PGPM modules
  • Running PostGraphile/GraphQL integration tests

Quick Start

Installation

bash
pnpm add -D pgsql-test

Basic Test Setup

typescript
import { getConnections } from 'pgsql-test';

let db: any;
let teardown: () => Promise<void>;

beforeAll(async () => {
  ({ db, teardown } = await getConnections());
});

afterAll(() => teardown());
beforeEach(() => db.beforeEach());
afterEach(() => db.afterEach());

test('database query works', async () => {
  const result = await db.query('SELECT 1 as num');
  expect(result.rows[0].num).toBe(1);
});

Core API

getConnections()

Creates an isolated test database and returns clients plus cleanup function.

typescript
import { getConnections } from 'pgsql-test';

const { db, teardown } = await getConnections(
  connectionOptions?,  // Optional: custom connection settings
  seedAdapters?        // Optional: array of seed adapters
);

Returns:

  • db - PgTestClient with query methods and transaction helpers
  • teardown - Cleanup function to drop the test database

PgTestClient Methods

MethodDescription
db.query(sql, params?)Execute SQL query
db.beforeEach()Start savepoint (call in beforeEach)
db.afterEach()Rollback to savepoint (call in afterEach)
db.setContext(key, value)Set session context variable
db.getPool()Get underlying pg Pool

Seeding Data

SQL File Seeding

typescript
import { getConnections, seed } from 'pgsql-test';

const { db, teardown } = await getConnections({}, [
  seed.sqlfile(['./fixtures/schema.sql', './fixtures/data.sql'])
]);

Function Seeding

typescript
import { getConnections, seed } from 'pgsql-test';

const { db, teardown } = await getConnections({}, [
  seed.fn(async (client) => {
    await client.query(`
      CREATE TABLE users (
        id SERIAL PRIMARY KEY,
        name TEXT NOT NULL
      )
    `);
    await client.query(`
      INSERT INTO users (name) VALUES ('Alice'), ('Bob')
    `);
  })
]);

PGPM Module Seeding

Deploy a PGPM module into the test database:

typescript
import { getConnections, seed } from 'pgsql-test';

const { db, teardown } = await getConnections({}, [
  seed.pgpm(process.cwd())  // Deploy module from current directory
]);

CSV Seeding

typescript
import { getConnections, seed } from 'pgsql-test';

const { db, teardown } = await getConnections({}, [
  seed.sqlfile(['./fixtures/schema.sql']),
  seed.csv('users', './fixtures/users.csv')
]);

Test Patterns

Transaction Isolation

Each test runs in a savepoint that gets rolled back:

typescript
beforeEach(() => db.beforeEach());  // Creates savepoint
afterEach(() => db.afterEach());    // Rolls back to savepoint

test('insert is isolated', async () => {
  await db.query("INSERT INTO users (name) VALUES ('Test')");
  // This insert is rolled back after the test
});

test('previous insert not visible', async () => {
  const result = await db.query("SELECT * FROM users WHERE name = 'Test'");
  expect(result.rows).toHaveLength(0);  // Rolled back!
});

Setting User Context

For RLS (Row Level Security) testing:

typescript
test('user can only see own data', async () => {
  await db.setContext('user_id', 'user-123');
  
  const result = await db.query('SELECT * FROM user_data');
  // Only returns rows where user_id = 'user-123'
});

Multiple Connections

typescript
const { db: adminDb, teardown: teardownAdmin } = await getConnections({
  user: 'postgres'
});

const { db: appDb, teardown: teardownApp } = await getConnections({
  user: 'app_user'
});

Running Tests

Prerequisites

  1. Start PostgreSQL:
bash
pgpm docker start
  1. Load environment:
bash
eval "$(pgpm env)"
  1. Run tests:
bash
pnpm test

One-liner

bash
pgpm env pnpm test

Watch Mode

bash
pgpm env pnpm test --watch

Common Workflows

Testing PGPM Module

typescript
import { getConnections, seed } from 'pgsql-test';

describe('my-module', () => {
  let db: any, teardown: () => Promise<void>;

  beforeAll(async () => {
    ({ db, teardown } = await getConnections({}, [
      seed.pgpm(__dirname + '/..')  // Deploy parent module
    ]));
  });

  afterAll(() => teardown());
  beforeEach(() => db.beforeEach());
  afterEach(() => db.afterEach());

  test('function works correctly', async () => {
    const result = await db.query('SELECT my_function($1)', ['input']);
    expect(result.rows[0].my_function).toBe('expected');
  });
});

Testing with Fixtures

typescript
import { getConnections, seed } from 'pgsql-test';
import path from 'path';

const fixtures = path.join(__dirname, '__fixtures__');

beforeAll(async () => {
  ({ db, teardown } = await getConnections({}, [
    seed.sqlfile([
      path.join(fixtures, 'schema.sql'),
      path.join(fixtures, 'seed-data.sql')
    ])
  ]));
});

Troubleshooting

IssueSolution
"Connection refused"Run pgpm docker start first
"Database does not exist"Check PGDATABASE env var or use pgpm env
Tests hangEnsure teardown() is called in afterAll
Data leaking between testsAdd beforeEach/afterEach savepoint calls
Permission deniedCheck database user has CREATE DATABASE permission
Slow testsUse savepoints instead of recreating database per test

File Structure

Recommended test file organization:

code
my-module/
  __tests__/
    __fixtures__/
      schema.sql
      seed-data.sql
    my-feature.test.ts
  deploy/
  revert/
  verify/
  pgpm.plan

References

For related skills:

  • Docker container management: See pgpm-docker skill
  • Environment variables: See pgpm-env skill
  • GraphQL codegen: See constructive-graphql-codegen skill