mock-drizzle-orm
v1.0.0
Published
Mock Drizzle ORM for testing - never hit the database again.
Maintainers
Readme
mock-drizzle-orm
Never hit the database again while testing Drizzle ORM.
Mock for unit tests against PostgreSQL, MySQL, and SQLite (async + sync). Works with any Node test runner (Vitest, Jest, Mocha, Node test, …) — the package has no Jest/Vitest/Sinon runtime dependency.
Zero refactoring required: new MockDrizzle() intercepts await and .execute() on your existing singleton db, including db.transaction().
See a full Vitest dogfood app in examples/vitest-pg-app (npm run test:example).
Install
npm install --save-dev mock-drizzle-orm
# peer
npm install drizzle-ormShips both ESM and CJS (import / require). Requires Node >= 18.
Lifecycle
describe("suite", () => {
let mock: MockDrizzle;
beforeEach(() => {
mock = new MockDrizzle();
});
afterEach(() => {
mock.restore(); // remove global intercept
});
});Use resetAll() between cases if you keep one instance for the whole file; prefer restore() when tearing down.
Quick start (zero-refactor)
Keep your production db module as-is:
// db.ts
import { drizzle } from "drizzle-orm/node-postgres";
export const db = drizzle(process.env.DATABASE_URL!, { schema });In tests:
import { MockDrizzle } from "mock-drizzle-orm";
import { db } from "./db";
import { accounts } from "./schema";
const mock = new MockDrizzle();
mock
.onMock(accounts)
.select()
.toReturn([{ id: 1, userId: 10, balance: 100 }])
.toReturn([{ id: 2, userId: 20, balance: 50 }]);
mock.onMock(accounts).update().toReturn([]).toReturn([]);
const result = await db.transaction(async (tx) => {
// your real service logic using tx / db
});
mock.onMock(accounts).update().assertCalledTimes(2);
mock.restore();MySQL / SQLite singletons work the same way (drizzle-orm/mysql-proxy, drizzle-orm/sqlite-proxy, drizzle-orm/better-sqlite3, etc.).
For sync SQLite (better-sqlite3), use .all() / .get() / .run() and sync transaction — no await required:
mock
.onMock(users)
.select()
.toReturn([{ id: 1, name: "Alice" }]);
const rows = db.select().from(users).all();
db.transaction((tx) => tx.select().from(users).all());Method-first mocking
mock.onMock(users).select().toReturn(data);
mock.onMock(users).insert().toReturn(data);
mock.onMock(users).update().toReturn(data);
mock.onMock(users).delete().toReturn(data);
// or by table name
mock.onMock("users").select().toReturn(data);Queue multiple returns (FIFO):
mock
.onMock(users)
.select()
.toReturn([{ id: 1 }])
.toReturn([{ id: 2 }]);Param matching
Optionally require toSQL().params to match:
mock
.onMock(users)
.select()
.withParams([1])
.toReturn([{ id: 1, name: "Alice" }]);
mock
.onMock(users)
.select()
.toReturn([{ id: 2 }]); // fallback when params do not matchDirect .execute()
await db.select().from(users).execute();is intercepted the same as await db.select().from(users).
Relational queries
db.query.users.findMany() / findFirst() use the same select queue:
mock
.onMock(users)
.select()
.toReturn([{ id: 1, name: "Alice" }]);
await db.query.users.findMany();
mock.onMock(users).select().toReturn({ id: 1, name: "Alice" });
await db.query.users.findFirst();Transactions
db.transaction(async (tx) => { ... }) is mocked for PG, MySQL, and SQLite: no real BEGIN/COMMIT. Queries inside the callback are intercepted like normal awaits. Nested tx.transaction(...) is supported without savepoints. tx.rollback() throws Drizzle’s dialect rollback error.
Assertions
await db.select().from(users);
mock.onMock(users).select().assertCalled();
mock.onMock(users).select().assertCalledTimes(1);
mock.onMock(users).select().getCalls(); // [{ table, operation, sql, params }, ...]Also available: mock.getCalls() for all operations.
Reset and restore
mock.onMock(users).select().reset(); // one operation queue
mock.onMock(users).reset(); // one table
mock.resetAll(); // clear queues; keep intercept
mock.restore(); // remove intercept + clear queuesOptional: explicit mock db (DI)
const mock = new MockDrizzle();
const db = mock.createDb({ schema: { users } }); // dialect defaults to 'pg'
const mysqlDb = mock.createDb({ dialect: "mysql", schema: { users } });
const sqliteDb = mock.createDb({ dialect: "sqlite", schema: { users } }); // async
const sqliteSyncDb = mock.createDb({ dialect: "sqlite-sync", schema: { users } }); // better-sqlite3-styleHow it works
new MockDrizzle() patches:
QueryPromise.prototype.thenand dialect*SelectBase.prototype.then(select copiesthenvia mixin)- Builder
_prepare(so.execute()/.prepare().execute()resolve mocks) PgDatabase/MySqlDatabase/BaseSQLiteDatabase.transaction
On await or execute, the package reads toSQL(), matches table + operation (+ optional params), and returns queued data. createDb() remains available for an isolated mock session.
Lint & format
npm run lint # oxlint
npm run lint:fix # auto-fix where safe
npm run fmt # oxfmt write
npm run fmt:check # CI checkLimitations
- Matching is by primary table + operation (+ optional params); join-aware routing and full
whereAST matching are not implemented - Transaction config (isolation level, etc.) is ignored
- Hoist a single
drizzle-ormversion so the patched prototypes match your app
License
MIT
