ship an oss add-on that:
- spins up a fresh test db (or schema) on first run.
- reuses it if current drizzle schema hash hasn’t changed.
- wraps every spec in a savepoint + rollback by default (== super fast).
- lets any spec flip to “real tx, then truncate” via a tiny ctx flag.
no bespoke wrappers; we piggyback on vitest’s built-in test context.
// vitest.setup.ts
import { setupTestDb, useTestDb } from 'drizzle-vitest';
// one-time boot + cache check (fns are async, so top-level await in ESM)
await setupTestDb({
schema: drizzleSchema,
url: process.env.TEST_DATABASE_URL,
});
// global hook – default = savepoint
import { beforeEach } from 'vitest';
import { useTestDb } from 'drizzle-vitest';
beforeEach(async (ctx) => {
// ctx is vitest’s TestContext; we just extend it
await useTestDb(ctx); // mode: 'savepoint'
});
// inside a spec that needs real tx nesting / ddl
import { test } from 'vitest';
test('heavy ddl stuff', async (ctx) => {
// switch this test to truncate mode on the fly
await ctx.$db.mode('truncate');
// … run crazy migrations …
});
- automatic creation –
setupTestDb
hashes compiled drizzle schema; if no cached db or hash mismatch, it migrates a fresh db. - savepoint strategy –
useTestDb(ctx)
opens a txn, sets a savepoint;afterEach
rolls back to it, keeping connection warm. - truncate strategy – same hook, but instead of a savepoint we just record table names and
truncate cascade
them inafterEach
. - ctx plumbing – vitest passes a mutable
TestContext
as the first arg to everybeforeEach
/test
; we augment it withctx.$db
so users can opt-in per spec.
// drizzle-vitest.d.ts
import { TestContext } from 'vitest';
declare module 'vitest' {
interface TestContext {
$db: {
mode: (m: 'savepoint' | 'truncate') => Promise<void>;
client: ReturnType<typeof drizzle>;
};
}
}