Table of Contents
Test Setup and Configuration
Essential setup files and configuration patterns:Client-Side Test Setup:Key Features:
// src/__tests__/setup-test.ts
import { beforeAll, afterEach } from 'vitest'
import { cleanup } from '@testing-library/react'
import '@testing-library/jest-dom'
// Configure jsdom environment
beforeAll(() => {
// Mock window objects
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
})
// Mock localStorage
Object.defineProperty(window, 'localStorage', {
value: {
getItem: vi.fn(),
setItem: vi.fn(),
removeItem: vi.fn(),
clear: vi.fn(),
},
})
// Mock ResizeObserver
global.ResizeObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(),
unobserve: vi.fn(),
disconnect: vi.fn(),
}))
})
// Cleanup after each test
afterEach(() => {
cleanup()
vi.clearAllMocks()
})- jsdom Environment: DOM testing for React components
- Mock Browser APIs: localStorage, matchMedia, ResizeObserver
- Automatic Cleanup: Prevents test pollution
- Jest DOM Matchers: Enhanced assertion capabilities
Mocking Patterns
Comprehensive mocking strategies for different types of dependencies:Authentication Service Mocking:Better Auth Session Mocking:
// src/services/__tests__/helper-service-test.ts
import { vi } from 'vitest'
import { getAuthUser } from '@/services/authentication/auth-service'
import { User } from '@/services/types/domain/user-types'
export const setupAuthUserMocked = async (user?: User | undefined) => {
return vi.mocked(getAuthUser).mockImplementation(async () => {
if (!user) return undefined
return user
})
}
// Usage patterns in tests
describe('Service with Authentication', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should allow access for authenticated user', async () => {
const testUser = createTestUser({ role: 'admin' })
setupAuthUserMocked(testUser)
const result = await protectedService()
expect(result).toBeDefined()
})
it('should deny access for unauthenticated user', async () => {
setupAuthUserMocked() // No user (undefined)
await expect(protectedService()).rejects.toThrow('Unauthorized')
})
})// Mock Better Auth context
vi.mock('@/lib/auth/auth-client', () => ({
authClient: {
getSession: vi.fn(),
signIn: vi.fn(),
signOut: vi.fn(),
},
}))
// Mock session data
const mockSession = {
user: {
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
},
session: {
id: 'session-123',
expiresAt: new Date(Date.now() + 86400000), // 24 hours
},
}
vi.mocked(authClient.getSession).mockResolvedValue(mockSession)Test Data Factories
Reusable test data generation patterns:User Data Factory:
// src/__tests__/factories/user-factory.ts
import { User } from '@/services/types/domain/user-types'
import { RoleConst } from '@/services/types/domain/auth-types'
export const createTestUser = (overrides: Partial<User> = {}): User => {
return {
id: crypto.randomUUID(),
name: 'Test User',
email: 'test@example.com',
emailVerified: true,
image: null,
role: RoleConst.USER,
visibility: 'private',
banned: null,
banReason: null,
banExpires: null,
createdAt: new Date(),
updatedAt: new Date(),
stripeCustomerId: null,
twoFactorEnabled: false,
...overrides,
}
}
// Specialized user factories
export const createAdminUser = (overrides: Partial<User> = {}): User => {
return createTestUser({
role: RoleConst.ADMIN,
name: 'Admin User',
email: 'admin@example.com',
...overrides,
})
}
export const createPublicUser = (overrides: Partial<User> = {}): User => {
return createTestUser({
visibility: 'public',
name: 'Public User',
email: 'public@example.com',
...overrides,
})
}
// Usage in tests
describe('User Service', () => {
it('should handle admin users', () => {
const adminUser = createAdminUser()
expect(adminUser.role).toBe(RoleConst.ADMIN)
})
})Common Utilities
Shared testing utilities and helper functions:Custom Assertion Utilities:
// src/__tests__/utils/assertions.ts
import { expect } from 'vitest'
// UUID validation
export const expectValidUUID = (value: string) => {
expect(value).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
}
// Date assertions
export const expectRecentDate = (date: Date | string, withinMs = 5000) => {
const dateObj = typeof date === 'string' ? new Date(date) : date
const now = new Date()
const diff = Math.abs(now.getTime() - dateObj.getTime())
expect(diff).toBeLessThan(withinMs)
}
// Email format validation
export const expectValidEmail = (email: string) => {
expect(email).toMatch(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
}
// Authorization error assertion
export const expectAuthorizationError = async (promise: Promise<any>) => {
await expect(promise).rejects.toThrowError('Accès non autorisé')
}
// Object structure validation
export const expectUserStructure = (user: any) => {
expect(user).toEqual(expect.objectContaining({
id: expect.stringMatching(/^[0-9a-f-]{36}$/),
email: expect.stringMatching(/^[^\s@]+@[^\s@]+\.[^\s@]+$/),
name: expect.any(String),
role: expect.stringMatching(/^(guest|user|moderator|admin|super_admin)$/),
createdAt: expect.any(Date),
}))
}
// Usage in tests
describe('User Service', () => {
it('should create user with valid structure', async () => {
const user = await createUserService(userData)
expectUserStructure(user)
expectValidUUID(user.id)
expectValidEmail(user.email)
expectRecentDate(user.createdAt)
})
})Best Practices
General Testing Principles:
- Consistency: Use factories and utilities for consistent test data
- Isolation: Each test should be independent and repeatable
- Clarity: Test names and structure should be self-documenting
- Performance: Mock external dependencies to keep tests fast
Key Recommendations:
- Use Factories: Create reusable data factories for consistent test data
- Mock External Services: Keep tests focused on your code, not third-party services
- Clean Up: Always clean up after tests to prevent pollution
- Type Safety: Leverage TypeScript for better test reliability
- Shared Utilities: Create helper functions for common testing patterns