Files
gh-djankies-claude-configs-…/skills/creating-client-singletons/references/test-pattern.md
2025-11-29 18:22:25 +08:00

260 lines
5.1 KiB
Markdown

# Test Pattern
Proper test setup with PrismaClient singleton ensures test isolation and prevents connection exhaustion.
## Test File Setup
**Import singleton, don't create:**
```typescript
import { prisma } from '@/lib/prisma'
describe('User operations', () => {
beforeEach(async () => {
await prisma.user.deleteMany()
})
it('creates user', async () => {
const user = await prisma.user.create({
data: { email: 'test@example.com' }
})
expect(user.email).toBe('test@example.com')
})
afterAll(async () => {
await prisma.$disconnect()
})
})
```
**Key Points:**
- Import singleton, don't create
- Clean state with `deleteMany` or transactions
- Disconnect once at end of suite
- Don't disconnect between tests (kills connection pool)
---
## Test Isolation with Transactions
**Better approach for test isolation:**
```typescript
import { prisma } from '@/lib/prisma'
import { PrismaClient } from '@prisma/client'
describe('User operations', () => {
let testPrisma: Omit<PrismaClient, '$connect' | '$disconnect' | '$on' | '$transaction' | '$use'>
beforeEach(async () => {
await prisma.$transaction(async (tx) => {
testPrisma = tx
await tx.user.deleteMany()
})
})
it('creates user', async () => {
const user = await testPrisma.user.create({
data: { email: 'test@example.com' }
})
expect(user.email).toBe('test@example.com')
})
})
```
**Why this works:**
- Each test runs in transaction
- Automatic rollback after test
- No data leakage between tests
- Faster than deleteMany
---
## Mocking PrismaClient for Unit Tests
**When to mock:**
- Testing business logic without database
- Fast unit tests
- CI/CD pipeline optimization
**File: `__mocks__/prisma.ts`**
```typescript
import { PrismaClient } from '@prisma/client'
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended'
export const prismaMock = mockDeep<PrismaClient>()
beforeEach(() => {
mockReset(prismaMock)
})
```
**File: `__tests__/userService.test.ts`**
```typescript
import { prismaMock } from '../__mocks__/prisma'
import { createUser } from '../services/userService'
jest.mock('@/lib/prisma', () => ({
__esModule: true,
default: prismaMock,
}))
describe('User Service', () => {
it('creates user with email', async () => {
const mockUser = { id: '1', email: 'test@example.com' }
prismaMock.user.create.mockResolvedValue(mockUser)
const user = await createUser('test@example.com')
expect(user.email).toBe('test@example.com')
expect(prismaMock.user.create).toHaveBeenCalledWith({
data: { email: 'test@example.com' }
})
})
})
```
**Key Points:**
- Mock the singleton module, not PrismaClient
- Reset mocks between tests
- Type-safe mocks with jest-mock-extended
- Fast tests without database
---
## Integration Test Setup
**File: `tests/setup.ts`**
```typescript
import { prisma } from '@/lib/prisma'
beforeAll(async () => {
await prisma.$connect()
})
afterAll(async () => {
await prisma.$disconnect()
})
export async function cleanDatabase() {
const tables = ['User', 'Post', 'Comment']
for (const table of tables) {
await prisma[table.toLowerCase()].deleteMany()
}
}
```
**File: `tests/users.integration.test.ts`**
```typescript
import { prisma } from '@/lib/prisma'
import { cleanDatabase } from './setup'
describe('User Integration Tests', () => {
beforeEach(async () => {
await cleanDatabase()
})
it('creates and retrieves user', async () => {
const created = await prisma.user.create({
data: { email: 'test@example.com' }
})
const retrieved = await prisma.user.findUnique({
where: { id: created.id }
})
expect(retrieved?.email).toBe('test@example.com')
})
it('handles unique constraint', async () => {
await prisma.user.create({
data: { email: 'test@example.com' }
})
await expect(
prisma.user.create({
data: { email: 'test@example.com' }
})
).rejects.toThrow(/Unique constraint/)
})
})
```
**Key Points:**
- Shared setup in `tests/setup.ts`
- Clean database between tests
- Test real database behavior
- Catch constraint violations
---
## Anti-Pattern: Creating Client in Tests
**WRONG:**
```typescript
import { PrismaClient } from '@prisma/client'
describe('User tests', () => {
let prisma: PrismaClient
beforeEach(() => {
prisma = new PrismaClient()
})
afterEach(async () => {
await prisma.$disconnect()
})
it('creates user', async () => {
const user = await prisma.user.create({
data: { email: 'test@example.com' }
})
expect(user.email).toBe('test@example.com')
})
})
```
**Problems:**
- New connection pool every test
- Connect/disconnect overhead
- Connection exhaustion in large suites
- Slow tests
**Fix:**
```typescript
import { prisma } from '@/lib/prisma'
describe('User tests', () => {
beforeEach(async () => {
await prisma.user.deleteMany()
})
it('creates user', async () => {
const user = await prisma.user.create({
data: { email: 'test@example.com' }
})
expect(user.email).toBe('test@example.com')
})
})
```
**Result:**
- Reuses singleton connection
- Fast tests
- No connection exhaustion