local-neon-testing
v0.1.10
Published
A local PostgreSQL implementation that mimics the neon-testing API for fast, offline integration testing
Maintainers
Readme
local-neon-testing
A local PostgreSQL implementation that mimics the neon-testing API for fast, offline integration testing.
Package size: 8.1 KB | Dependencies: pg only | License: MIT
Overview
This library provides the clean API and per-file isolation of neon-testing whilst using local PostgreSQL for faster tests, offline development, and zero cost. The API is identical, allowing seamless switching between local and cloud testing.
Key benefits:
- Each test file gets isolated PostgreSQL database
- Automatic database creation and cleanup
- Optional zero-config mode (auto-starts PostgreSQL with Docker)
- 100% API compatible with neon-testing
- TypeScript native with full type definitions
- Pure JavaScript (no cmake, Python, or native build tools required)
When to Use This
Local development:
- Faster feedback loops (local database)
- Work offline without network
- Zero cost (no cloud branches)
- Same clean API as neon-testing
Can swap to real neon-testing in CI for production parity testing by changing one import.
Installation
Zero config (auto-starts PostgreSQL):
npm install -D local-neon-testing vitest @testcontainers/postgresqlRequires Docker running.
Use existing PostgreSQL:
npm install -D local-neon-testing vitestRequires PostgreSQL with CREATEDB permission.
Quick Start
Step 1: Configure
// tests/local-neon.ts
import { makeLocalNeonTesting } from "local-neon-testing";
export const neonTesting = makeLocalNeonTesting({
// Option A: Omit connectionString = auto-starts PostgreSQL (requires Docker)
// Option B: Provide connectionString = use existing PostgreSQL
connectionString: "postgresql://user:password@localhost:5432/postgres",
runMigrations: async (databaseUrl: string) => {
// Optional: run your migrations here
},
});Step 2: Use in Tests
// tests/integration/users.test.ts
import { describe, test, expect } from "vitest";
import { neonTesting } from "../local-neon.ts";
import { Pool } from "pg";
const getBranch = neonTesting();
describe("User API", () => {
test("creates user successfully", async () => {
const branch = getBranch();
const pool = new Pool({ connectionString: branch.database_url });
await pool.query(`CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)`);
await pool.query(`INSERT INTO users (name) VALUES ('Alice')`);
const result = await pool.query(`SELECT * FROM users`);
expect(result.rows).toHaveLength(1);
expect(result.rows[0].name).toBe('Alice');
await pool.end();
});
});That's it. Each test file gets an isolated database, migrations run automatically, cleanup happens automatically.
How It Works
Per-file isolation:
beforeAll: Creates new PostgreSQL database (test_abc123def456)- Runs migrations if
runMigrationsprovided - Sets
process.env.DATABASE_URLto test database - Tests execute against isolated database
afterAll: Drops database and cleans up
Test files run in parallel (Vitest default), each with complete isolation. Tests within a file share the same database and run sequentially.
API Reference
makeLocalNeonTesting(options)
Creates a configured lifecycle function for your tests.
interface LocalNeonTestingOptions {
connectionString?: string;
deleteBranch?: boolean;
autoCloseWebSockets?: boolean;
schemaOnly?: boolean;
roleName?: string;
databaseName?: string;
runMigrations?: (databaseUrl: string) => Promise<void>;
autoStartPostgres?: boolean;
}Options:
connectionString- Base PostgreSQL connection. If omitted, auto-starts PostgreSQL with testcontainersdeleteBranch- Delete database after tests (default:true)autoCloseWebSockets- Compatibility flag for neon-testing (ignored)schemaOnly- Create empty database without migrations (default:false)runMigrations- Function to run schema migrations on test databaseautoStartPostgres- Auto-start PostgreSQL if no connectionString (default:true)
Lifecycle Function
const neonTesting = makeLocalNeonTesting({ ... });
// In test file:
const getBranch = neonTesting();
// Access branch information:
const branch = getBranch();
console.log(branch.id); // "test_abc123def456"
console.log(branch.database_url); // Full connection string
console.log(branch.project_id); // "local"
console.log(branch.created_at); // ISO timestampOverride Options Per Test File
const getBranch = neonTesting({
schemaOnly: true, // Skip migrations for this test
deleteBranch: false, // Keep database for debugging
});Cleanup Utility
// Delete all databases starting with "test_"
await neonTesting.deleteAllTestBranches();Configuration Examples
With Drizzle ORM
import { makeLocalNeonTesting } from "local-neon-testing";
import { drizzle } from "drizzle-orm/node-postgres";
import { migrate } from "drizzle-orm/node-postgres/migrator";
import { Pool } from "pg";
export const neonTesting = makeLocalNeonTesting({
connectionString: "postgresql://localhost:5432/postgres",
runMigrations: async (databaseUrl) => {
const pool = new Pool({ connectionString: databaseUrl });
const db = drizzle(pool);
await migrate(db, { migrationsFolder: "./drizzle" });
await pool.end();
},
});With Prisma
import { makeLocalNeonTesting } from "local-neon-testing";
import { exec } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
export const neonTesting = makeLocalNeonTesting({
connectionString: "postgresql://localhost:5432/postgres",
runMigrations: async (databaseUrl) => {
await execAsync(`DATABASE_URL="${databaseUrl}" npx prisma migrate deploy`);
},
});With Raw SQL Migrations
import { makeLocalNeonTesting } from "local-neon-testing";
import { Pool } from "pg";
import { readFile } from "fs/promises";
export const neonTesting = makeLocalNeonTesting({
connectionString: "postgresql://localhost:5432/postgres",
runMigrations: async (databaseUrl) => {
const pool = new Pool({ connectionString: databaseUrl });
const sql = await readFile("./migrations/001_init.sql", "utf-8");
await pool.query(sql);
await pool.end();
},
});Switching to Real Neon Testing
The API is 100% compatible with neon-testing. To use real Neon branches in CI:
// Development (local-neon-testing)
import { makeLocalNeonTesting } from "local-neon-testing";
export const neonTesting = makeLocalNeonTesting({
connectionString: "postgresql://localhost:5432/postgres",
});
// CI/Production (real neon-testing)
import { makeNeonTesting } from "neon-testing";
export const neonTesting = makeNeonTesting({
apiKey: process.env.NEON_API_KEY!,
projectId: process.env.NEON_PROJECT_ID!,
});No test code changes required.
Utilities
lazySingleton
Creates a lazy singleton from a factory function:
import { lazySingleton } from "local-neon-testing/utils";
import { Pool } from "pg";
const getPool = lazySingleton(() =>
new Pool({ connectionString: process.env.DATABASE_URL })
);
test("my test", async () => {
const pool = getPool(); // Created once, reused
const result = await pool.query("SELECT 1");
});Troubleshooting
"Permission denied to create database"
PostgreSQL user needs CREATEDB permission:
ALTER USER your_user CREATEDB;"Database already exists"
Clean up old test databases:
await neonTesting.deleteAllTestBranches();"Failed to connect" or "ECONNREFUSED"
Check:
- PostgreSQL is running
- Connection string is correct
- User has access to specified database
Tests fail with "relation does not exist"
Verify:
runMigrationsfunction executes correctly- Migration files are accessible
schemaOnlyis nottrue
Comparison
| Feature | local-neon-testing (auto) | local-neon-testing (manual) | neon-testing | |---------|--------------------------|----------------------------|--------------| | Speed | Fast | Fastest | Slower (cloud) | | Network | None | None | Required | | Cost | Free | Free | Branch creation costs | | PostgreSQL Install | Not needed | Required | Not needed | | Docker | Required | Not needed | Not needed | | Production Parity | PostgreSQL | PostgreSQL | Exact Neon | | API | Identical | Identical | Identical |
Examples
See the examples directory:
basic.test.ts- Basic usagewith-migrations.test.ts- Migration setupisolation.test.ts- Test isolation demonstrationzero-config.test.ts- Automatic PostgreSQL mode
Development
Setup:
bun installRun tests (requires PostgreSQL or Docker):
# With existing PostgreSQL
TEST_DATABASE_URL="postgresql://localhost:5432/postgres" bun test
# Or with zero-config (auto-starts PostgreSQL with Docker)
bun testBuild:
bun run buildSee CONTRIBUTING.md for full guidelines.
Acknowledgements
Inspired by neon-testing by Mikael Lirbank. This library provides a local alternative whilst maintaining API compatibility.
License
MIT - see LICENSE
Author
Lee Crossley
Related:
- neon-testing - Cloud-based Neon branch testing
- Neon - Serverless PostgreSQL
- @testcontainers/postgresql - PostgreSQL testcontainers
