A simple and type-safe mocking utility for Prisma Client in Bun tests.
- 🔒 Fully type-safe mocking of Prisma Client
- 🔄 Support for nested models and methods
- 🧹 Simple reset mechanism for test isolation
- 🪲 Handles edge cases gracefully
- 🚀 Zero dependencies - only requires Bun
# Install as a dev dependency
bun add -d bun-mock-prisma
Create a singleton file for your Prisma mock:
// prisma.singleton.ts
import { mock } from 'bun:test'
import { PrismaClient } from '@prisma/client'
import { createPrismaMock } from 'bun-mock-prisma'
import type { PrismaClientMock } from 'bun-mock-prisma'
import { prisma } from '@/utils/prisma'
// Mock the module that exports your Prisma client
mock.module('@/utils/prisma', () => ({
__esModule: true,
prisma: createPrismaMock<PrismaClient>(),
}))
// Export the mocked client for use in tests
export const prismaMock = prisma as unknown as PrismaClientMock<PrismaClient>
// user.test.ts
import { describe, test, expect } from 'bun:test'
import { prismaMock } from './prisma.singleton'
import { createUser } from './user.service'
// Reset mocks before each test
beforeEach(() => {
prismaMock._reset()
})
describe('User service', () => {
test('should create a new user', async () => {
// Setup mock return value
prismaMock.user.create.mockResolvedValue({
id: '1',
name: 'Test User',
email: 'test@example.com',
createdAt: new Date(),
})
// Call the service that uses prisma
const user = await createUser({
name: 'Test User',
email: 'test@example.com',
})
// Verify results
expect(user.id).toBe('1')
expect(user.name).toBe('Test User')
// Verify mock was called correctly
expect(prismaMock.user.create).toHaveBeenCalledWith({
data: {
name: 'Test User',
email: 'test@example.com',
},
})
})
})
To publish this package to the Bun registry:
# Login to Bun registry
bun login
# Build the package
bun run build
# Publish to Bun registry
bun publish
test('should find posts with their author', async () => {
// Setup mock return value for posts with included author
prismaMock.post.findMany.mockResolvedValue([
{
id: '1',
title: 'Test Post',
content: 'This is a test post',
authorId: '1',
author: {
id: '1',
name: 'Test User',
email: 'test@example.com',
createdAt: new Date(),
updatedAt: new Date(),
},
},
])
// Call the service that uses prisma
const posts = await getPosts()
// Verify results
expect(posts).toHaveLength(1)
expect(posts[0].title).toBe('Test Post')
expect(posts[0].author.name).toBe('Test User')
// Verify mock was called correctly
expect(prismaMock.post.findMany).toHaveBeenCalledWith({
include: { author: true },
})
})
test('should handle database errors', async () => {
// Setup mock to throw an error
prismaMock.user.create.mockRejectedValue(
new Error('Unique constraint violation')
)
// Call the service and expect it to throw
await expect(
createUser({ name: 'Test', email: 'existing@example.com' })
).rejects.toThrow('Unique constraint violation')
})
When testing code that uses Prisma transactions, we recommend focusing on testing the individual operations rather than the transaction mechanism itself:
// Service with transaction
class UserService {
// Method that can be tested directly
async createUserWithPost(client, userData, postData) {
const user = await client.user.create({ data: userData })
const post = await client.post.create({
data: { ...postData, authorId: user.id },
})
return { user, post }
}
// Method that uses transaction - not tested directly
async createUserWithPostTransaction(userData, postData) {
return prisma.$transaction((tx) =>
this.createUserWithPost(tx, userData, postData)
)
}
}
// Test
test('should create user and post correctly', async () => {
const service = new UserService()
// Setup mocks
prismaMock.user.create.mockResolvedValue({
id: '1',
name: 'Test User',
email: 'test@example.com',
})
prismaMock.post.create.mockResolvedValue({
id: '1',
title: 'Test Post',
content: 'This is a test post',
authorId: '1',
})
// Test the implementation directly with the mock client
const result = await service.createUserWithPost(
prismaMock,
{ name: 'Test User', email: 'test@example.com' },
{ title: 'Test Post', content: 'This is a test post' }
)
// Verify results
expect(result.user).toBeTruthy()
expect(result.post).toBeTruthy()
// Verify mocks were called
expect(prismaMock.user.create).toHaveBeenCalled()
expect(prismaMock.post.create).toHaveBeenCalled()
})
When setting up multiple mocks that will be called sequentially, be aware that in some cases mocks may interfere with each other. Use more specific assertions instead of direct object equality:
// Instead of this:
expect(result.user).toEqual(mockUser)
// Use individual property checks:
expect(result.user.id).toBe('1')
expect(result.user.name).toBe('Test User')
// Or check for existence:
expect(result.user).toBeTruthy()
expect(result.user.id).toBeTruthy()
The mocking system preserves complex query parameters so you can verify them:
prismaMock.user.findMany.mockResolvedValue([
/* results */
])
await prisma.user.findMany({
where: {
OR: [
{ name: { contains: 'User' } },
{ email: { endsWith: '@example.com' } },
],
AND: [{ active: true }],
},
orderBy: { createdAt: 'desc' },
take: 10,
skip: 5,
})
expect(prismaMock.user.findMany).toHaveBeenCalledWith({
where: {
OR: [
{ name: { contains: 'User' } },
{ email: { endsWith: '@example.com' } },
],
AND: [{ active: true }],
},
orderBy: { createdAt: 'desc' },
take: 10,
skip: 5,
})
Creates a mock instance that mimics the structure of your Prisma Client.
A type that represents the mocked Prisma Client, with all methods converted to Bun's Mock objects.
-
_reset()
: Resets all mocks. Call this inbeforeEach
to ensure test isolation.
No, you don't. Bun's mocking system works differently from Jest's. When you call mock.module()
in your singleton file, it automatically applies the mock for all tests that import that module.
No. Just import your singleton file that contains the mock setup at the top of your test files, and everything will work automatically.
We recommend testing the individual operations rather than the transaction mechanism. Separating your business logic from the transaction mechanism makes testing easier.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
MIT