Comprehensive unit testing setup for the Dirt Free CRM application using Jest and React Testing Library.
This project uses two types of tests:
- Unit Tests (Jest) - Component, utility, and API logic tests
- E2E Tests (Playwright) - Full application integration tests
- Jest - Testing framework
- @testing-library/react - React component testing utilities
- @testing-library/jest-dom - DOM matchers
- @testing-library/user-event - User interaction simulation
- jest-environment-jsdom - Browser-like test environment
jest.config.js - Main Jest configuration
- Uses Next.js Jest config for proper module resolution
- Excludes Playwright tests and server-only tests
- Coverage thresholds: 70% for branches, functions, lines, statements
- Module name mapping for
@/imports
jest.setup.js - Test environment setup
- Loads
@testing-library/jest-dommatchers - Mocks Next.js router and navigation
- Mocks browser APIs (matchMedia, IntersectionObserver, ResizeObserver)
- Suppresses expected console warnings
# Run all unit tests
npm run test:unit
# Run tests in watch mode
npm run test:unit:watch
# Run tests with coverage report
npm run test:unit:coverage
# Run tests in CI mode
npm run test:unit:ci# Run E2E tests
npm test
# Run with UI mode
npm run test:ui
# Run specific test file
npm run test:validation# Run both unit and E2E tests
npm run test:allsrc/
__tests__/
lib/
loyalty/
tiers.test.ts
promotions/
targeting.test.ts
api/
response-helpers.test.ts
utils/
formatters.test.ts
lib/
test-utils.tsx # Test utilities and helpers
import { render } from '@/lib/test-utils'
// Renders with all necessary providers
render(<MyComponent />)import { createMockSupabaseClient } from '@/lib/test-utils'
const mockSupabase = createMockSupabaseClient()
mockSupabase.from('users').select.mockResolvedValue({
data: [{ id: '1', name: 'Test User' }],
error: null,
})import {
createMockCustomer,
createMockJob,
createMockInvoice,
createMockUser,
createMockPromotion,
createMockLoyaltyCustomer,
createMockReview,
createMockOpportunity,
createMockReferral,
} from '@/lib/test-utils'
// Create test data with defaults
const customer = createMockCustomer()
// Override specific fields
const premiumCustomer = createMockCustomer({
name: 'Premium Customer',
lifetime_value: 5000,
})import { createSuccessResponse, createErrorResponse } from '@/lib/test-utils'
const successResponse = createSuccessResponse({ id: '123', name: 'Test' })
const errorResponse = createErrorResponse('not_found', 'Resource not found', 404)import { mockFetch, createMockFetchResponse } from '@/lib/test-utils'
// Mock successful fetch
mockFetch(createMockFetchResponse({ data: 'test' }))
// Mock fetch error
mockFetchError('Network error')import { describe, it, expect } from '@jest/globals'
describe('Loyalty Tiers', () => {
it('should qualify for Bronze tier with 0-499 points', () => {
const points = 250
const tier = points >= 1000 ? 'Gold' : points >= 500 ? 'Silver' : 'Bronze'
expect(tier).toBe('Bronze')
})
})import { render, screen, fireEvent } from '@/lib/test-utils'
import MyComponent from './MyComponent'
describe('MyComponent', () => {
it('should render and respond to clicks', () => {
const handleClick = jest.fn()
render(<MyComponent onClick={handleClick} />)
const button = screen.getByRole('button')
fireEvent.click(button)
expect(handleClick).toHaveBeenCalledTimes(1)
})
})import { createMockSupabaseClient } from '@/lib/test-utils'
describe('API Route', () => {
it('should return data from database', async () => {
const mockSupabase = createMockSupabaseClient()
mockSupabase.from('customers').select.mockResolvedValue({
data: [{ id: '1', name: 'Test' }],
error: null,
})
// Test your API logic here
})
})Coverage reports are generated in the /coverage directory.
# Generate coverage report
npm run test:unit:coverage
# View HTML report
open coverage/lcov-report/index.html- Branches: 70%
- Functions: 70%
- Lines: 70%
- Statements: 70%
- Keep tests close to the code they test
- Use descriptive test names
- Group related tests with
describeblocks
- Aim for 70%+ coverage on critical paths
- Test edge cases and error conditions
- Test user interactions, not implementation details
- Mock external dependencies (Supabase, APIs)
- Use test data factories for consistent test data
- Don't mock what you own (internal utilities)
- Use specific matchers (
toBe,toEqual,toHaveLength) - Test one thing per test case
- Make assertions meaningful
it('should handle async operations', async () => {
const result = await asyncFunction()
expect(result).toBe('expected')
})npm run test:unit -- path/to/test.test.tsnpm run test:unit -- --testNamePattern="loyalty"Add to .vscode/launch.json:
{
"type": "node",
"request": "launch",
"name": "Jest Debug",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand", "--no-cache"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}- name: Run Unit Tests
run: npm run test:unit:ci
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.infoSolution: Increase timeout in jest.config.js or individual tests
it('long test', async () => {
// test code
}, 10000) // 10 second timeoutSolution: Check moduleNameMapper in jest.config.js
Solution: Always return promises or use async/await
it('should wait', async () => {
await waitFor(() => expect(element).toBeInTheDocument())
})Current test suite:
- Test Suites: 4 passed
- Tests: 74 passed
- Coverage: Run
npm run test:unit:coverageto see current coverage
Place test file next to the code or in __tests__ directory:
src/lib/utils/myUtil.ts
src/lib/utils/__tests__/myUtil.test.ts
import { describe, it, expect } from '@jest/globals'
import { render, screen } from '@/lib/test-utils'describe('MyUtil', () => {
it('should do something', () => {
const result = myUtil('input')
expect(result).toBe('expected')
})
})npm run test:unitnpm run test:unit -- -unpm run test:unit -- --clearCachenpm run test:unit -- --verbose