DEV Community

Cover image for How to Test Your Node.js & Postgres App Using Drizzle & PGlite
Benjamin Daniel
Benjamin Daniel

Posted on

How to Test Your Node.js & Postgres App Using Drizzle & PGlite

I was recently tasked with writing tests for a large-scale production codebase serving over 2.1 million users. During my conversation with the lead engineer he mentioned something that stood out: “One of our biggest pain points is our test setup.”

That conversation made me realize, that the approach I’ve been using might actually be more useful than I thought. This post walks through that setup.


The Stack

We mostly use PostgreSQL in production and write functional or integration tests, which means no database mocking. In the past, we relied on Testcontainers, to spin up real Postgres instances, but that approach was, slow and heavy. You can find my other article on Testcontainers here: The death of mocks by Testcontainers

Eventually, we went looking for lighter alternatives.

That’s when we found @electric-sql/pglite, the WASM build of Postgres from the great people at ElectricSQL. It’s really perfect for a huge chunk of tests and it gets the job done with zero Docker overhead.


What We Use


The Core Idea

At test time, we dynamically swap the production database with a PG LITE instance.

Here’s how.


Swapping the DB Connection in Tests

// test/setup.ts
import { DatabaseConnection } from "@src/app.bind";
import { container } from "../app.container";
import { drizzle } from "drizzle-orm/pglite";

container.unbindSync(DatabaseConnection); // Remove existing binding

const db = drizzle({ schema }); // Use pglite instead of Postgres

container
  .bind(DatabaseConnection)
  .toConstantValue(db as unknown as DatabaseConnection); // Rebind with test DB

Enter fullscreen mode Exit fullscreen mode

Running Migrations in Tests

To make sure your schema is applied to the in-memory DB before tests run, we do this:

beforeAll(async () => {
  const { createRequire } =
    await vi.importActual<typeof import("node:module")>("node:module");
  // @ts-expect-error import isn't allowed in ESM
  const require = createRequire(import.meta.url);
  const { pushSchema } =
    require("drizzle-kit/api") as typeof import("drizzle-kit/api");

  const { apply } = await pushSchema(schema, db as unknown as never);
  await apply();

  container
    .rebindSync(DatabaseConnection)
    .toConstantValue(db as unknown as DatabaseConnection);
});
Enter fullscreen mode Exit fullscreen mode

This pushes your schema to the PG LITE DB before any tests run, you can find more about drizzle push schema here https://orm.drizzle.team/docs/drizzle-kit-push.


Be Careful

Behavior may diverge silently from production in edge cases, I haven't run into these kind of scenarios yet.

If you’re tired of:

  • Over-engineered mocks
  • Docker-heavy CI pipelines
  • Testing strategies that don’t resemble prod

…then this setup might be worth trying.

Want to see how we structure actual tests with this setup? I’ll share more in the next post, kindly follow me for that.

Or drop a comment if you want the full boilerplate repo.

Top comments (0)