PG Testing Guide
Use this guide when writing or reviewing tests that use @faasjs/pg or @faasjs/pg-dev in FaasJS projects.
Applicable Scenarios
- Adding or modifying query-builder usage in application code
- Modifying shared query helpers, repository wrappers, or table types
- Updating schema or migration helpers
- Writing integration tests for
@faasjs/pgor@faasjs/pg-dev
Default Workflow
- Prefer
PgVitestPlugin()so Vitest registers a lazy temporary database bootstrap, starts PGlite on the firstawait getClient(), runs migrations, backfillsDATABASE_URL, and clears table contents before later tests in the same file. In mixed workspaces, keep PG-backed tests in a Node project because the plugin skipsjsdomandhappy-domprojects. - Use
await getClient()to seed data and run assertions so app code and tests share the same async bootstrap path. - Add only the suite-specific setup or fixtures that the plugin does not already provide.
- Pair runtime assertions with
expectTypeOf(...)when query inference, declaration merging, or shared wrappers affect types. - Run the smallest validation command that matches the change surface.
Mixed-workspace example:
import { PgVitestPlugin } from '@faasjs/pg-dev'
import { defineConfig } from 'vite-plus'
export default defineConfig({
plugins: [PgVitestPlugin()],
test: {
projects: [
{
extends: true as const,
test: {
name: 'node',
include: ['src/**/*.test.ts'],
exclude: ['src/**/*.types.test.ts'],
environment: 'node',
},
},
{
extends: true as const,
test: {
name: 'ui',
include: ['src/**/*.test.tsx', 'src/**/*.ui.test.ts'],
environment: 'jsdom',
},
},
],
},
})
Minimal Example
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { PgVitestPlugin } from '@faasjs/pg-dev'
export default defineConfig({
plugins: [PgVitestPlugin()],
})
import { describe, expect, it } from 'vitest'
import { getClient } from '@faasjs/pg'
async function seedUser() {
const client = await getClient()
await client.query('users').insert({
id: 1,
name: 'Alice',
})
}
describe('users query', () => {
it('selects seeded rows', async () => {
const client = await getClient()
await seedUser()
await expect(client.query('users').where('id', 1)).resolves.toMatchObject([{ name: 'Alice' }])
})
})
Rules
1. Every behavior change needs a test update
- Add a new test or update an existing one for every runtime behavior change.
- Prefer focused tests near the feature or query helper you changed instead of broad catch-all suites.
- Test the database behavior the caller relies on, not private implementation details.
2. Type-sensitive changes need expectTypeOf(...)
- Add or update type assertions when changing inference, overloads, declaration merging, or shared wrappers.
- If a query-builder method or helper changes result shape, test the inferred result type directly.
- Keep runtime coverage and type coverage aligned when a change affects both.
3. Keep tests close to the feature they protect
- Put query tests near the repository or helper layer that owns the query.
- Keep schema or migration tests near the migration utilities or setup they validate.
- Store shared test bootstrap close to Vitest config or other test support code.
- If you contribute to
@faasjs/pgitself, follow the feature-area test layout already used in the package.
4. Keep tests isolated even when the plugin resets data
- Prefer
PgVitestPlugin()to reset rows automatically before each test. - Create extra tables, seed data, or temp folders explicitly when a suite goes beyond the default migrations.
- Do not rely on hidden state from another test file or case.
5. Use @faasjs/pg-dev through the Vitest plugin
- Prefer
PgVitestPlugin()for workspace test runs. - PG-backed runtime tests are still Node runtime tests. Prefer the regular
nodeproject, but if only that subset needs project-level setup, use a node-scoped project name such asnode-pginstead of a standalone runtime bucket likepg. - In tests, let the plugin lazy-bootstrap the default client through
await getClient(). If a suite also readsprocess.env.DATABASE_URLdirectly, trigger the bootstrap withawait getClient()first. - Reach for
createClient(process.env.DATABASE_URL, options)only when a suite genuinely needs custompostgres.jsoptions or an extra connection after the bootstrap URL exists. - Keep lower-level database bootstrapping internal to the test support layer; public examples should only show the plugin.
See Also
- Testing Guide — shared testing principles (apply first)
- PG Query Builder and Raw SQL Guide — building queries under test
- PG Table Types Guide — table type definitions for typed assertions
- PG Schema and Migrations Guide — migrations run by the plugin
Review Checklist
- runtime behavior changes have test coverage
- type-sensitive changes have
expectTypeOf(...)coverage - tests live close to the feature area that changed
- mixed workspaces keep PG-backed tests in a Node project because browser-like projects are skipped
- pg-backed runtime tests stay in a node-scoped project (
nodeornode-pg) in mixed workspaces - suites either rely on the plugin reset or clean up their own extra setup
- validation commands match the change surface