Seed a Test Database with Faker and a Fixture Seed
Generate deterministic fake rows with faker, pin the random seed so every run produces the same data, and reset the database between test suites with a transaction rollback.
Task
Populate a test database with fake but realistic rows that are identical on every run and reset cheaply between tests.
When to use this
Tests share a database and one suite leaves rows that break the next. The team wants each suite to start from a known dataset without a slow truncate between every test.
Prerequisites
- Node.js 20 LTS with faker installed
- A database that supports nested transactions or savepoints
- A fixture seed number committed to the repo
Steps
1. Pin faker to a fixed seed so every run produces the same sequence of values.
import { faker } from "@faker-js/faker";
faker.seed(42);
Expected output: Two runs produce identical rows, so a failing test reproduces the same data every time.
2. Generate a batch of rows with realistic constraints, like unique emails and valid date ranges.
const users = Array.from({ length: 100 }, () => ({
email: faker.string.uuid() + "@test.example",
created: faker.date.past(),
}));
Expected output: A hundred rows with unique emails and past created dates, ready to insert.
3. Wrap each test in a transaction that rolls back on teardown so no rows leak between suites.
beforeEach(async () => { tx = await client.query("BEGIN"); });
afterEach(async () => { await client.query("ROLLBACK"); });
Expected output: A test that inserts a user leaves no row for the next test, and the suite runs in a fraction of a second.
Troubleshooting
Problem
A test commits despite the rollback wrapper
Solution
Use a client wrapper that swallows COMMIT and issues ROLLBACK instead, so a stray commit cannot leak state.
Problem
faker versions change the output for the same seed
Solution
Pin faker to an exact version in the lockfile, and regenerate the seed snapshot on a major bump.
Limitations
- A rollback wrapper does not reset sequences; a serial id can still climb across tests, which matters if a test asserts on a specific id.
- faker produces realistic but not valid data for every domain; add custom generators for domain-specific fields.
Deterministic fakes
Pin faker to a fixed seed so every run produces the same rows. A failing test reproduces the same data, and a diff in the snapshot means a real change, not a random one.
import { faker } from '@faker-js/faker';
faker.seed(42);
const users = Array.from({ length: 100 }, () => ({
email: faker.string.uuid() + '@test.example',
created: faker.date.past(),
}));
Rollback, do not truncate
Wrap each test in a transaction and roll back on teardown. No rows leak between suites, and the reset takes milliseconds instead of a full truncate. Wrap the client so a stray commit cannot escape the rollback.
beforeEach(async () => { tx = await client.query('BEGIN'); });
afterEach(async () => { await client.query('ROLLBACK'); });
Expected outcome
Every run produces the same rows, every test starts from a clean database, and the suite runs fast because the reset is a rollback. Pin faker to an exact version so a bump does not silently change the seed output.