Skip to content

Latest commit

 

History

History
223 lines (177 loc) · 6.22 KB

File metadata and controls

223 lines (177 loc) · 6.22 KB
sidebar_position 11
title Mocking Drizzle
description How to mock Drizzle database instances in your unit tests

Mocking Drizzle

:::info Overview For an overview of the pattern and approach to mocking ORMs, see the Mocking ORMs overview. :::

:::tip Complete Examples For complete, runnable Drizzle examples, see the Drizzle examples in the Suites Examples repository. :::

Drizzle uses a database instance that you typically import directly. Wrap it in an injectable class.

Step 1: Create a Database Injectable

import { Injectable } from "@nestjs/common";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema";

@Injectable()
export class DatabaseService {
  private db: ReturnType<typeof drizzle>;

  constructor() {
    const pool = new Pool({
      connectionString: process.env.DATABASE_URL,
    });
    this.db = drizzle(pool, { schema });
  }

  getDb() {
    return this.db;
  }
}

Step 2: Create a Repository Wrapper

import { Injectable } from "@nestjs/common";
import { DatabaseService } from "./database.service";
import { users } from "./schema";
import { eq } from "drizzle-orm";

@Injectable()
export class UserRepository {
  constructor(private readonly database: DatabaseService) {}

  async findById(id: number) {
    const db = this.database.getDb();
    const result = await db
      .select()
      .from(users)
      .where(eq(users.id, id))
      .limit(1);
    return result[0] || null;
  }

  async findByEmail(email: string) {
    const db = this.database.getDb();
    const result = await db
      .select()
      .from(users)
      .where(eq(users.email, email))
      .limit(1);
    return result[0] || null;
  }

  async create(email: string, name: string) {
    const db = this.database.getDb();
    const result = await db.insert(users).values({ email, name }).returning();
    return result[0];
  }
}

Step 3: Use the Repository in Your Service

import { Injectable } from "@nestjs/common";
import { UserRepository } from "./user.repository";

@Injectable()
export class UserService {
  constructor(private readonly userRepository: UserRepository) {}

  async getUserById(id: number) {
    return this.userRepository.findById(id);
  }

  async createUser(email: string, name: string) {
    const existingUser = await this.userRepository.findByEmail(email);
    if (existingUser) {
      throw new Error("User already exists");
    }

    return this.userRepository.create(email, name);
  }
}

Step 4: Test with Suites

import { TestBed, type Mocked } from "@suites/unit";
import { UserService } from "./user.service";
import { UserRepository } from "./user.repository";

describe("UserService", () => {
  let userService: UserService;
  let userRepository: Mocked<UserRepository>;

  beforeAll(async () => {
    const { unit, unitRef } = await TestBed.solitary(UserService).compile();
    userService = unit;
    userRepository = unitRef.get(UserRepository);
  });

  it("should get user by id", async () => {
    const mockUser = { id: 1, email: "test@example.com", name: "Test User" };
    userRepository.findById.mockResolvedValue(mockUser);

    const result = await userService.getUserById(1);

    expect(result).toEqual(mockUser);
    expect(userRepository.findById).toHaveBeenCalledWith(1);
  });

  it("should create a new user", async () => {
    userRepository.findByEmail.mockResolvedValue(null);
    const newUser = { id: 1, email: "new@example.com", name: "New User" };
    userRepository.create.mockResolvedValue(newUser);

    const result = await userService.createUser("new@example.com", "New User");

    expect(result).toEqual(newUser);
    expect(userRepository.findByEmail).toHaveBeenCalledWith("new@example.com");
    expect(userRepository.create).toHaveBeenCalledWith(
      "new@example.com",
      "New User"
    );
  });
});

Direct Database Injection

If you prefer to inject DatabaseService directly and mock the database instance:

import { Injectable } from "@nestjs/common";
import { DatabaseService } from "./database.service";
import { users } from "./schema";
import { eq } from "drizzle-orm";

@Injectable()
export class UserService {
  constructor(private readonly database: DatabaseService) {}

  async getUserById(id: number) {
    const db = this.database.getDb();
    const result = await db
      .select()
      .from(users)
      .where(eq(users.id, id))
      .limit(1);
    return result[0] || null;
  }
}
import { TestBed, type Mocked } from "@suites/unit";
import { UserService } from "./user.service";
import { DatabaseService } from "./database.service";

describe("UserService", () => {
  let userService: UserService;
  let database: Mocked<DatabaseService>;

  beforeAll(async () => {
    const { unit, unitRef } = await TestBed.solitary(UserService).compile();
    userService = unit;
    database = unitRef.get(DatabaseService);
  });

  it("should get user by id", async () => {
    const mockDb = {
      select: jest.fn().mockReturnThis(),
      from: jest.fn().mockReturnThis(),
      where: jest.fn().mockReturnThis(),
      limit: jest
        .fn()
        .mockResolvedValue([{ id: 1, email: "test@example.com" }]),
    };
    database.getDb.mockReturnValue(mockDb as any);

    const result = await userService.getUserById(1);

    expect(result).toEqual({ id: 1, email: "test@example.com" });
    expect(database.getDb).toHaveBeenCalled();
  });
});

Summary

  • Wrap Drizzle database instance in an injectable DatabaseService class to make it mockable
  • Create repository wrappers for clean separation between data access and business logic
  • Use Suites to automatically mock repository dependencies in your service tests
  • Direct database injection is possible but requires chained mock setup

Next Steps