askill
vitest-prisma-dual-query-mocking

vitest-prisma-dual-query-mockingSafety 100Repository

Mock Prisma/ZenStack queries when code uses both findFirst and findUnique on the same model. Use when: (1) Tests fail with "Cannot read property of undefined", (2) Code queries same model with different methods (findFirst for search, findUnique for exact match), (3) Mock returns undefined for one query type, (4) Single mockImplementation doesn't cover all cases. Covers Vitest + Prisma/ZenStack testing patterns with multiple query methods.

0 stars
1.2k downloads
Updated 2/7/2026

Package Files

Loading files...
SKILL.md

Vitest Prisma Dual Query Mocking

Problem

When code uses both findFirst and findUnique on the same Prisma/ZenStack model, a single mock implementation doesn't work correctly. One query returns the mocked data while the other returns undefined, causing test failures.

Context / Trigger Conditions

  • Test error: "Cannot read properties of undefined (reading 'value')" or similar
  • Code uses db.model.findFirst({ where: {...} }) for one query
  • Code uses db.model.findUnique({ where: { composite_key: {...} } }) for another query
  • Both queries target the same model (e.g., userPreference)
  • Single mock like mockDb.model.findFirst = vi.fn().mockResolvedValue(data) doesn't cover both
  • Tests pass individually but fail when both queries run in same test

Example failing test:

// This mock only works for findFirst, not findUnique
mockDb.userPreference.findFirst = vi.fn().mockResolvedValue({
  userId,
  key: "email.notification.messaging",
  value: "true",
});

// This query works
const enabled = await db.userPreference.findFirst({ where: { userId, key: "..." } });

// This query returns undefined (not mocked)
const cooldown = await db.userPreference.findUnique({
  where: { userId_key: { userId, key: "..." } }
});

console.log(cooldown); // undefined - causes test failure

Solution

Create separate mock implementations for each query method, each checking the query parameters to return appropriate data.

Pattern 1: Separate Mocks for Each Method

import { vi, beforeEach, it, expect } from "vitest";

// Mock database client
const mockDb = {
  userPreference: {
    findFirst: vi.fn(),
    findUnique: vi.fn(),
  },
} as unknown as typeof db;

beforeEach(() => {
  vi.clearAllMocks();
});

it("should handle dual query pattern", async () => {
  const userId = "test-user-id";

  // Mock findFirst for enabled/disabled check
  mockDb.userPreference.findFirst = vi.fn().mockImplementation(({ where }) => {
    if (where.key === "email.notification.messaging") {
      return Promise.resolve({
        userId,
        key: "email.notification.messaging",
        value: "true",
      });
    }
    return Promise.resolve(null);
  });

  // Mock findUnique for composite key lookup (separate implementation)
  mockDb.userPreference.findUnique = vi.fn().mockImplementation(({ where }) => {
    if (where.userId_key?.key === "email.notification.messaging.lastSent") {
      return Promise.resolve({
        userId,
        key: "email.notification.messaging.lastSent",
        value: new Date().toISOString(),
      });
    }
    return Promise.resolve(null);
  });

  // Both queries now work correctly
  const result = await myFunction(mockDb, userId);

  expect(mockDb.userPreference.findFirst).toHaveBeenCalledWith({
    where: { userId, key: "email.notification.messaging" },
  });
  expect(mockDb.userPreference.findUnique).toHaveBeenCalledWith({
    where: {
      userId_key: {
        userId,
        key: "email.notification.messaging.lastSent",
      },
    },
  });
});

Pattern 2: Conditional Mock Based on Query Structure

it("should mock based on query structure", async () => {
  // Single mock that handles both query types
  mockDb.userPreference.findFirst = vi.fn().mockImplementation(({ where }) => {
    // Route based on query structure
    if (where.key === "specific.key") {
      return Promise.resolve({ userId, key: where.key, value: "data" });
    }
    return Promise.resolve(null);
  });

  mockDb.userPreference.findUnique = vi.fn().mockImplementation(({ where }) => {
    // Check for composite key pattern
    if (where.userId_key) {
      const key = where.userId_key.key;
      return Promise.resolve({ userId, key, value: "data" });
    }
    return Promise.resolve(null);
  });
});

Pattern 3: Using Vitest Mock Libraries

For complex scenarios, use specialized mocking libraries:

import { createPrismaMock } from 'prisma-mock-vitest';
import { beforeEach, expect, test } from 'vitest';

let client: PrismaClient;

beforeEach(async () => {
  client = await createPrismaMock();
});

test("automated mocking handles both queries", async () => {
  // Library automatically handles findFirst, findUnique, and other methods
  const result = await myFunction(client, userId);
  expect(result).toBeDefined();
});

Verification

  1. Check Mock Calls: Verify both mocks are called with correct parameters

    expect(mockDb.model.findFirst).toHaveBeenCalledTimes(1);
    expect(mockDb.model.findUnique).toHaveBeenCalledTimes(1);
    
  2. Test Isolation: Each mock should only respond to its specific query pattern

    // findFirst should not respond to composite key queries
    const result = await mockDb.model.findFirst({
      where: { userId_key: { userId, key } }
    });
    expect(result).toBeNull(); // Should not match
    
  3. Return Value Validation: Ensure mocked data structure matches Prisma schema

    const data = await mockDb.model.findUnique({ where: { id: "123" } });
    expect(data).toHaveProperty("id");
    expect(data).toHaveProperty("userId");
    

Example

Real-World Scenario: Email preference system checking both enabled state and cooldown.

// Code being tested
async function shouldSendEmail(db: DB, userId: string) {
  // Query 1: Check if notifications are enabled (findFirst)
  const preference = await db.userPreference.findFirst({
    where: { userId, key: "email.notification.messaging" }
  });

  if (preference?.value !== "true") {
    return { send: false };
  }

  // Query 2: Check cooldown timestamp (findUnique with composite key)
  const cooldown = await db.userPreference.findUnique({
    where: {
      userId_key: {
        userId,
        key: "email.notification.messaging.lastSent",
      },
    },
  });

  if (cooldown) {
    const lastSent = new Date(cooldown.value);
    // Check if within cooldown period...
  }

  return { send: true };
}

// Test with separate mocks
it("should check both enabled state and cooldown", async () => {
  const thirtyMinutesAgo = new Date(Date.now() - 30 * 60 * 1000);

  // Mock findFirst (enabled check)
  mockDb.userPreference.findFirst = vi.fn().mockImplementation(({ where }) => {
    if (where.key === "email.notification.messaging") {
      return Promise.resolve({
        userId: "test-user",
        key: "email.notification.messaging",
        value: "true", // Enabled
      });
    }
    return Promise.resolve(null);
  });

  // Mock findUnique (cooldown check)
  mockDb.userPreference.findUnique = vi.fn().mockImplementation(({ where }) => {
    if (where.userId_key?.key === "email.notification.messaging.lastSent") {
      return Promise.resolve({
        userId: "test-user",
        key: "email.notification.messaging.lastSent",
        value: thirtyMinutesAgo.toISOString(), // Recent send
      });
    }
    return Promise.resolve(null);
  });

  const result = await shouldSendEmail(mockDb, "test-user");

  expect(result.send).toBe(false); // Blocked by cooldown
  expect(mockDb.userPreference.findFirst).toHaveBeenCalledTimes(1);
  expect(mockDb.userPreference.findUnique).toHaveBeenCalledTimes(1);
});

Notes

  • Query Structure Matters: findFirst uses where: { field: value }, findUnique uses where: { uniqueConstraint: { field: value } } - check this in mocks
  • Mock Isolation: Each mock should ONLY respond to its specific query pattern
  • Clear All Mocks: Always call vi.clearAllMocks() in beforeEach to prevent test pollution
  • Type Safety: Use as unknown as typeof db to satisfy TypeScript when creating mock objects
  • Library Options: Consider prisma-mock-vitest, vitest-prisma-mock, or prisma-mock for complex scenarios instead of manual mocking
  • Debugging: Log where parameter in mockImplementation to see what queries are being made
  • Common Mistake: Mocking only findFirst when code also uses findUnique (or vice versa)

References

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

78/100Analyzed 4/12/2026

Well-structured technical skill about mocking Prisma dual queries in Vitest. Has clear trigger conditions, 3 implementation patterns with code examples, verification steps, and real-world scenario. Content is actionable and accurate. penalties apply for deeply nested path in config/ folder suggesting internal config use, and generic 'claude-config' repo name indicates personal/organizational utility rather than public skill library.

100
90
75
90
90

Metadata

Licenseunknown
Version1.0.0
Updated2/7/2026
PublisherHankanman

Tags

databasegithubtesting