@integration-testing/data-isolation
v0.1.2
Published
Transactional database integration tests for Jest and Vitest with Prisma, pg, and TypeORM
Maintainers
Readme
@integration-testing/data-isolation
Write isolated database integration tests with a consistent API across supported test runners and database clients.
Your repository test inserts a product, updates its stock, and checks the result. The test passes. But the product is still in the database. Run the test again and the same SKU may already exist; run another test and it may see data left behind by the first.
Now every test needs a cleanup strategy: delete the rows it created, handle foreign keys in the right order, and make sure cleanup still runs when an assertion fails.
Give each test a transaction, then roll back its writes automatically.
That is what this library provides through rollback-only transactions. After your application's
real migrations have prepared the database, each test gets its own transaction. Its fixtures,
test body, and teardown use the same transaction-scoped client. When the attempt finishes, the
library rolls back those writes, whether the test passed or failed. You do not write per-test
DELETE cleanup for operations made through that client.
Configure your database and test runner once, then choose one of the two declarations below for each transactional test file. Both use the same shared context and runner setup.
Option 1: annotation
Add @DataIntegrationTest to a marker class in the test file, then write ordinary tests:
import { DataIntegrationTest } from '@integration-testing/data-isolation';
import { dataContext } from './data-context.js';
@DataIntegrationTest
export class ProductRepositoryTest {}
test('creates a product', async () => {
const client = dataContext.getCurrentContext().client;
await client.query('INSERT INTO products (sku, stock) VALUES ($1, $2)', ['book', 10]);
const result = await client.query('SELECT stock FROM products WHERE sku = $1', ['book']);
expect(result.rows[0].stock).toBe(10);
}); // The insert is rolled back, including when the assertion fails.The annotation activates the entire file, including nested suites. The class is only a marker;
keep test bodies in your runner's test() or it() calls.
Option 2: without decorators
Call declareDataIntegrationTest() at the top level of the test file instead of adding a marker class:
import { declareDataIntegrationTest } from '@integration-testing/data-isolation';
import { dataContext } from './data-context.js';
declareDataIntegrationTest();
test('creates a product', async () => {
const client = dataContext.getCurrentContext().client;
await client.query('INSERT INTO products (sku, stock) VALUES ($1, $2)', ['book', 10]);
const result = await client.query('SELECT stock FROM products WHERE sku = $1', ['book']);
expect(result.rows[0].stock).toBe(10);
}); // The insert is rolled back, including when the assertion fails.Both examples use pg and your runner's globals after one-time setup. They have the same
transaction behavior: beforeEach, the test, and afterEach share a client, followed by rollback.
Use exactly one declaration per test file; do not combine the two forms. Neither declaration
replaces the database and runner setup.
Two tools that work hand in hand
Real infrastructure with @integration-testing/testcontainers, isolated test data with @integration-testing/data-isolation.
| Tool | Responsibility | | --- | --- | | Testcontainers Integration | Start disposable infrastructure and stop it after the run | | Data Isolation | Give each test a transaction-scoped client and roll back its writes | | Your application | Apply its real migrations before test workers start and pass the scoped client to repositories |
Start with the paired PostgreSQL example,
which uses the published @integration-testing/[email protected] package. Both tools remain
independent: an existing test database, Docker Compose, or a CI database service can also supply
the connection URL.
Install and configure once
For the 0.1.2 release, install:
npm install --save-dev @integration-testing/[email protected]The lifecycle is database-independent: configure a transaction adapter for your database client.
Requires Node.js 22.22+. Supports Vitest 4.1.x, Jest 30.x, pg 8.x, and TypeORM 0.3.x. Install only the runner and database client you use; runner and driver dependencies stay optional. Core, database adapters, and Jest integration support ESM and CommonJS; the Vitest integration uses ESM. Before npm publication, use the local archive instructions.
- Export a shared context with
createDataIntegrationTestContext(configuration). - Install that context in your runner setup. Jest also requires the package's
/jest/environment. - Run your application's real migrations before workers start.
- Add
@DataIntegrationTestordeclareDataIntegrationTest()to each test file and usedataContext.getCurrentContext().client.
The setup guide provides complete files and commands. Choose your runnable example below to see the wiring in context.
Examples
These projects live in this repository and are not shipped to npm. Each example README explains its prerequisites and commands; the links to setup files show the actual implementation.
| What you want to do | Runnable example and setup |
| --- | --- |
| Declare a file with @DataIntegrationTest | Annotation example |
| Declare a file without decorators | Function example |
| Start PostgreSQL with Testcontainers and use pg + Vitest | Small paired example, database context |
| Use pg, Prisma, or TypeORM with Jest and Vitest | NestJS inventory application, shared context |
| Configure Jest | Jest configuration, setup |
| Configure Vitest | Vitest configuration, setup |
| Use an existing PostgreSQL URL without Testcontainers | Inventory run instructions: test:external |
| Try Prisma + SQLite without Docker | Small SQLite example, inventory SQLite mode |
| Inject a transaction-bound client into NestJS repositories | Provider overrides, repository implementations |
| Apply real application migrations once | Inventory migrations and launcher |
| Compare with handwritten transaction hooks | Comparison, pg baseline for both runners |
The inventory example exercises the same application behavior across clients and runners, including constraints, multi-write operations, and rollback verified from an independent connection. Its installed-package check copies the project outside the workspace and installs a packed archive. See consumer verification.
What it does and does not do
Use this library for direct database and repository tests whose operations can use the supplied transaction client.
| The library handles | Your application supplies |
| --- | --- |
| A new transaction for each test attempt, including retries | A reachable test database and real application migrations |
| One typed client across beforeEach, the test, and afterEach | Awaited operations using that client |
| Rollback after passing and failing tests, with cleanup failures reported | Repository or NestJS provider wiring |
| Jest and Vitest lifecycle integration | A shared context and the runner setup |
Rollback covers awaited operations using the supplied transaction client. The library does not patch production clients or automatically capture other connections, HTTP requests, background jobs, explicit commits, or nontransactional database operations. It does not undo messages, files, or other external effects.
Run real migrations once before workers for a shared database. prepareDatabase is per file,
so it is not the place for shared schema preparation. beforeAll and afterAll are outside
per-test transactions. Tests within a file must run sequentially; .concurrent and callback-style
done tests/hooks are unsupported. Parallel files still need fixtures that account for database
locks and shared state. Runner timeouts cannot forcibly cancel arbitrary JavaScript.
The core can be integrated with other runners, but Cucumber and custom harnesses need their own runner adapter. A declaration alone does not supply that integration.
Ordinary rollback hooks can be enough for a small suite. This library provides a reusable typed context and consistent lifecycle and failure reporting across supported runners and clients. No performance or maintenance savings are claimed without measurement.
For details, see the rollback boundaries, API and troubleshooting, and Testcontainers resource handoff.
Contributing and release checks
bun install --frozen-lockfile
bun run verify # Build, types, lint, runner tests, SQLite, package checks
bun run test:consumer # Installed consumer matrix; PostgreSQL requires DockerSee the release guide for the complete release gates and publication steps.
MIT license.
