Skip to content

Commit d78e15b

Browse files
committed
feat: Vitest environment for Prisma and PostgreSQL
Introduces a Vitest test environment for Prisma and PostgreSQL that runs each test inside a database transaction which is rolled back after the test finishes. This keeps tests isolated while allowing users to seed the database once before test execution, eliminating the need to reseed between tests or set up data dependencies for each test individually.
0 parents  commit d78e15b

22 files changed

+4748
-0
lines changed

.github/workflows/ci.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Continuous Integration
2+
3+
on:
4+
pull_request:
5+
branches: [ main ]
6+
push:
7+
branches: [ main ]
8+
workflow_dispatch:
9+
10+
jobs:
11+
main:
12+
name: Continuous Integration
13+
runs-on: ubuntu-latest
14+
strategy:
15+
matrix:
16+
node-version: [20.x, 22.x, 24.x]
17+
steps:
18+
- uses: actions/checkout@v4
19+
- name: Use Node.js ${{ matrix.node-version }}
20+
uses: actions/setup-node@v1
21+
with:
22+
node-version: ${{ matrix.node-version }}
23+
- uses: pnpm/action-setup@v4
24+
with:
25+
version: 10
26+
- name: Install Dependencies
27+
run: |
28+
pnpm install --frozen-lockfile
29+
- name: Lint, check formatting and types
30+
run: |
31+
pnpm run lint
32+
pnpm run format:check
33+
pnpm run typecheck
34+
- name: Test
35+
run: |
36+
pnpm run test
37+
- name: Build
38+
run: |
39+
pnpm run build

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
coverage
2+
dist
3+
node_modules
4+
test/prisma-client
5+
.DS_Store

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
save-exact=true

.release-it.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { Config } from 'release-it';
2+
3+
export default {
4+
git: {
5+
requireCleanWorkingDir: false,
6+
commit: true,
7+
tag: true,
8+
push: true,
9+
},
10+
github: {
11+
release: true,
12+
},
13+
npm: {
14+
publish: true,
15+
},
16+
plugins: {
17+
'@release-it/conventional-changelog': {
18+
infile: 'CHANGELOG.md',
19+
header: '# Changelog',
20+
preset: {
21+
name: 'conventionalcommits',
22+
types: [
23+
{ type: 'feat', section: '✨ New Features' },
24+
{ type: 'perf', section: '🚀 Performance' },
25+
{ type: 'fix', section: '🐛 Bug Fixes' },
26+
{ type: 'ci', section: '⚙️ CI' },
27+
{ type: 'build', hidden: true },
28+
{ type: 'docs', section: '📚 Documentation' },
29+
{ type: 'chore', hidden: true },
30+
{ type: 'refactor', hidden: true },
31+
{ type: 'test', hidden: true },
32+
],
33+
},
34+
},
35+
},
36+
} satisfies Config;

.vscode/extensions.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"recommendations": ["biomejs.biome"]
3+
}

.vscode/settings.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"biome.enabled": true,
3+
"editor.formatOnSave": true,
4+
"editor.defaultFormatter": "biomejs.biome",
5+
"editor.codeActionsOnSave": {
6+
"source.fixAll.biome": "always"
7+
},
8+
"editor.indentSize": 2
9+
}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Christoph Werner
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
![vitest-environment-prisma-postgres](https://i.imgur.com/KUxDm4b.png)
2+
3+
<div align="center">
4+
<h1>vitest-environment-prisma-postgres</h1>
5+
<a href="https://www.npmjs.com/package/vitest-environment-prisma-postgres"><img src="https://img.shields.io/npm/v/vitest-environment-prisma-postgres.svg?style=flat" /></a>
6+
<a href="https://github.com/prisma/prisma/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" /></a>
7+
<br />
8+
<br />
9+
<a href="#features">Features</a>
10+
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
11+
<a href="#installation--setup">Installation & Setup</a>
12+
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
13+
<a href="#typescript-configuration">TypeScript Configuration</a>
14+
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
15+
<a href="#known-limiations">Known Limitations</a>
16+
<br />
17+
<hr />
18+
</div>
19+
20+
## Motivation
21+
22+
[Vitest](https://vitest.dev/) environment for [Prisma](https://www.prisma.io/) + [PostgreSQL](https://www.postgresql.org/) designed for fast integration tests.
23+
24+
Integration tests against a database are often slow because each test needs its own database state. Teams typically either:
25+
26+
- Reseed the database before every test, or
27+
- Insert all required data inside each test.
28+
29+
Both approaches are slow, repetitive, and dominate test runtime.
30+
31+
**This environment eliminates that cost.**
32+
33+
You run migrations and seed your test database once.
34+
Each test then runs inside its own database transaction, which is rolled back automatically after the test finishes. Your tests stay isolated, realistic, and extremely fast, creating dedicated data for every test.
35+
36+
## Features
37+
38+
- Run integration tests against a real PostgreSQL instance.
39+
- Seed your database once at the beginning of the test run.
40+
- Tests run inside sandboxed transactions, but application-level transactions still work normally.
41+
- Test transactions are rolled back after every test.
42+
- Tests are isolated, fast, and order independent.
43+
44+
## Installation & Setup
45+
46+
#### Step 1: Install the environment
47+
48+
First, install the environnment:
49+
50+
```shell
51+
npm install vitest-environment-prisma-postgres --save-dev
52+
```
53+
54+
#### Step 2: Ensure peer dependency availability
55+
56+
Then, ensure that the required peer dependencies are available. This library requires that you have all of the following installed in your project:
57+
58+
- `vitest` in version `4.x`
59+
- `prisma` in version `7.x`
60+
- `@prisma/adapter-pg` in version `7.x`
61+
62+
#### Step 3: Enable and configure the environment in your Vitest config
63+
64+
Configure the environment in your Vitest config:
65+
66+
```ts
67+
import { defineConfig } from 'vitest/config';
68+
69+
export default defineConfig({
70+
test: {
71+
environment: 'prisma-postgres',
72+
environmentOptions: {
73+
'prisma-postgres': {
74+
// You must configure the path to your prisma client.
75+
clientPath: "./generated/prisma-client"
76+
}
77+
},
78+
setupFiles: [
79+
// Registers hooks that start and roll back a database transaction around every test.
80+
'vitest-environment-prisma-postgres/setup',
81+
// This is where you mock your Prisma client to use the test environment's client.
82+
'./vitest.setup.ts'
83+
],
84+
}
85+
});
86+
```
87+
88+
#### Step 4: Provide a `DATABASE_URL`
89+
90+
This environment will create the Prisma client and PostgreSQL adapter for your tests, so it has to know the connectionString to your test database.
91+
92+
You provide it by running your tests with a `DATABASE_URL` environment variable, which must point to a PostgreSQL instance for testing. It can point to:
93+
94+
- a real local PostgresSQL instance
95+
- a docker-compose container
96+
- a Testcontainers-created instance (see below)
97+
- a cloud-hosted PostgresSQL instance, e.g, Supabase or Prisma Postgres
98+
99+
#### Step 5: Mock Prisma client
100+
101+
In your setupFile, `vitest.setup.ts`, mock your local Prisma client with the client provided by this environment:
102+
103+
```ts
104+
import { vi } from 'vitest';
105+
106+
vi.mock('./generated/prisma-client', () => ({
107+
default: globalThis.prismaPostgresTestContext.client,
108+
}));
109+
```
110+
111+
This ensures that your application code uses the Prisma client created by the test environment. Combined with the vitest-environment-prisma-postgres/setup file, which starts and rolls back a transaction around every test, this means all Prisma queries from your code run inside an isolated transaction per test.
112+
113+
Please make sure that you're mocking exactly the module path that your code is using to import your Prisma client.
114+
115+
#### Step 6: Seed once per test run
116+
117+
Make sure to seed your test database at the beginning of every test run.
118+
119+
## TypeScript configuration
120+
121+
If you are using TypeScript, make sure to add this environment to your `compilerOptions.types` array in your `tsconfig.json`:
122+
123+
```json
124+
{
125+
"compilerOptions": {
126+
"types": [
127+
"node",
128+
"vitest/globals",
129+
"vitest-environment-prisma-postgres"
130+
]
131+
}
132+
}
133+
```
134+
135+
This is required because this environment provides a global type declaration:
136+
137+
```ts
138+
declare global {
139+
var prismaPostgresTestContext: PublicPrismaPostgresTestContext;
140+
}
141+
```
142+
143+
Without adding the package to `compilerOptions.types`, TypeScript will not include this global augmentation, and you will get type errors when mocking your Prisma client in `vitest.setup.ts`:
144+
145+
```ts
146+
vi.mock('./generated/prisma-client', () => ({
147+
default: globalThis.prismaPostgresTestContext.client,
148+
// ^^^^^^^^^^^^^^^^^^^^^^^^^
149+
// Error: Element implicitly has an 'any' type because
150+
// type 'typeof globalThis' has no index signature.
151+
}));
152+
```
153+
154+
155+
Adding "vitest-environment-prisma-postgres" to `compilerOptions.types` ensures that the global declaration is loaded and the mock is type-safe.
156+
157+
## Known limitations
158+
159+
- Support for [Vitest pools](https://vitest.dev/config/pool.html#pool) set to `vmThreads` or `vmThreads` is not implemented
160+
- This environment assumes that tests inside a single worker run one at a time:
161+
- Do not use `test.concurrent` for tests that touch the database.
162+
- Keep `maxConcurrency` at `1` for DB integration tests so a worker does not run multiple DB tests at once.
163+
164+
## License
165+
166+
MIT

biome.jsonc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/2.3.7/schema.json",
3+
"files": {
4+
"includes": ["**", "!!dist"]
5+
},
6+
"formatter": {
7+
"enabled": true,
8+
"formatWithErrors": false,
9+
"attributePosition": "auto",
10+
"indentStyle": "space",
11+
"indentWidth": 2,
12+
"lineWidth": 80,
13+
"lineEnding": "lf"
14+
},
15+
"linter": {
16+
"rules": {
17+
"style": {
18+
"noNonNullAssertion": "off"
19+
},
20+
"suspicious": {
21+
"noExplicitAny": "off"
22+
}
23+
}
24+
},
25+
"javascript": {
26+
"formatter": {
27+
"arrowParentheses": "always",
28+
"bracketSameLine": false,
29+
"bracketSpacing": true,
30+
"jsxQuoteStyle": "double",
31+
"quoteProperties": "asNeeded",
32+
"semicolons": "always",
33+
"trailingCommas": "all",
34+
"quoteStyle": "single"
35+
}
36+
},
37+
"json": {
38+
"formatter": {
39+
"trailingCommas": "none"
40+
}
41+
}
42+
}

package.json

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"name": "vitest-environment-prisma-postgres",
3+
"version": "1.0.0-alpha.0",
4+
"description": "",
5+
"type": "module",
6+
"scripts": {
7+
"build": "tsup",
8+
"lint": "biome lint",
9+
"format": "biome format --write",
10+
"format:check": "biome format",
11+
"release": "release-it",
12+
"test": "vitest run --coverage",
13+
"typecheck": "tsc --noEmit"
14+
},
15+
"keywords": [
16+
"vitest",
17+
"prisma",
18+
"postgres",
19+
"prisma orm",
20+
"postgesql",
21+
"test",
22+
"testing",
23+
"test isolation",
24+
"transaction"
25+
],
26+
"exports": {
27+
".": {
28+
"types": "./dist/index.d.ts",
29+
"require": "./dist/index.js",
30+
"import": "./dist/index.js"
31+
},
32+
"./setup": {
33+
"require": "./dist/setup.js",
34+
"import": "./dist/setup.js"
35+
}
36+
},
37+
"author": "Christoph Werner <christoph@codepunkt.de>",
38+
"license": "MIT",
39+
"devDependencies": {
40+
"@biomejs/biome": "2.3.7",
41+
"@prisma/adapter-pg": "7.0.1",
42+
"@types/node": "24.10.1",
43+
"@vitest/coverage-v8": "4.0.14",
44+
"prisma": "7.0.1",
45+
"release-it": "19.0.6",
46+
"tsup": "8.5.1",
47+
"typescript": "5.9.3",
48+
"vitest": "4.0.14"
49+
},
50+
"peerDependencies": {
51+
"@prisma/adapter-pg": ">=4 <8",
52+
"prisma": ">=4 <8",
53+
"vitest": ">=4 <5"
54+
}
55+
}

0 commit comments

Comments
 (0)