vitest-prisma-dual-query-mocking
Installation
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
-
Check Mock Calls: Verify both mocks are called with correct parameters
expect(mockDb.model.findFirst).toHaveBeenCalledTimes(1); expect(mockDb.model.findUnique).toHaveBeenCalledTimes(1); -
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 -
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:
findFirstuseswhere: { field: value },findUniqueuseswhere: { 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()inbeforeEachto prevent test pollution - Type Safety: Use
as unknown as typeof dbto satisfy TypeScript when creating mock objects - Library Options: Consider
prisma-mock-vitest,vitest-prisma-mock, orprisma-mockfor complex scenarios instead of manual mocking - Debugging: Log
whereparameter in mockImplementation to see what queries are being made - Common Mistake: Mocking only
findFirstwhen code also usesfindUnique(or vice versa)
References
Weekly Installs
2
Repository
hankanman/claude-configFirst Seen
Mar 4, 2026
Security Audits
Installed on
qoder2
gemini-cli2
claude-code2
github-copilot2
windsurf2
codex2