Testing database logic requires real Postgres with actual constraints, triggers, and RLS policies—not mocks or simulators. But spinning up test databases shouldn't slow you down.
In this lesson, you'll learn how to create ephemeral (temporary) Postgres databases for testing with pgsql-test. These databases are fast, isolated, and automatically cleaned up after your tests run.
Prerequisites
Ensure Postgres is running and database users are initialized as shown in prerequisites.
Why Ephemeral Databases?
Ephemeral databases give each test suite a fresh, isolated database that's automatically created and destroyed. No shared state, no manual cleanup, no conflicts. Fast and automatic.
Setting Up Your Workspace
Create a pnpm workspace optimized for Modular Postgres with pgpm:
Create a module:
Enter module details (name: pets, select uuid-ossp extension). Then navigate to the module:
Your First Ephemeral Database Test
Inside of __tests__/basic.test.ts:
Run the test:
Note: If you experience connection issues, see Tests fail to connect to database.
You should see:
What just happened? When you called getConnections(), pgsql-test:
- Generated a unique database name (UUID-based)
- Created the database in Postgres
- Connected a client to it
- Returned a
dbclient andteardownfunction
When teardown() runs, the database is dropped and connections are closed. Everything is automatic.
Testing with Real Schema
Now let's add a schema and table to test. Add a schema change:
Edit deploy/schemas/pets.sql:
Add a table change:
Edit deploy/schemas/pets/tables/pets.sql:
Now update __tests__/basic.test.ts to test your table:
Run the tests:
pgsql-test automatically deploys your module before running tests. Each test starts with a clean database state thanks to beforeEach() and afterEach().
Understanding Transaction Isolation
The beforeEach() and afterEach() hooks provide transaction-based isolation:
beforeEach(): Starts a transaction and creates a savepoint afterEach(): Rolls back to the savepoint and commits the transaction
This means:
- Each test runs in its own transaction
- Changes are automatically rolled back after each test
- Tests are completely isolated from each other
- No manual cleanup needed
This is much faster than recreating the database for each test. The database schema persists, but data changes are rolled back.
Key Takeaways
- Ephemeral databases provide complete isolation between test runs
- Use
pgpm addto create schemas and tables for testing - getConnections() automatically creates and deploys your module to test databases
- Transaction isolation with
beforeEach()/afterEach()keeps tests clean - Real Postgres means testing actual constraints, triggers, and features
- Fast performance enables tight feedback loops
What's Next
You've learned how to spin up ephemeral Postgres databases for testing. In the next lesson, we'll explore how to test Row-Level Security (RLS) policies by simulating different users and roles.
Testing RLS is where pgsql-test really shines—you'll see how to verify that your security policies actually work as intended.
