Testing Guidelines
Testing standards, structure, and best practices
Testing Guidelines
Comprehensive testing guidelines including test structure, coverage requirements, and testing utilities.
Test Structure
Component Testing
// __tests__/components/product-card.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { ProductCard } from '@/components/products/product-card'
import { mockProduct } from '@/lib/test-utils/mocks'
describe('ProductCard', () => {
it('renders product information correctly', () => {
render(<ProductCard product={mockProduct} />)
expect(screen.getByText(mockProduct.name)).toBeInTheDocument()
expect(screen.getByText(mockProduct.sku)).toBeInTheDocument()
})
it('calls onEdit when edit button is clicked', () => {
const onEdit = jest.fn()
render(<ProductCard product={mockProduct} onEdit={onEdit} />)
fireEvent.click(screen.getByRole('button', { name: /edit/i }))
expect(onEdit).toHaveBeenCalledWith(mockProduct)
})
})
Service Testing
// __tests__/services/product-service.test.ts
import { productService } from '@/lib/services/product'
import { mockProduct } from '@/lib/test-utils/mocks'
describe('ProductService', () => {
beforeEach(() => {
jest.clearAllMocks()
})
describe('createProduct', () => {
it('creates a product successfully', async () => {
const productData = {
name: 'Test Product',
sku: 'TEST-001',
price: 29.99
}
const result = await productService.create(productData)
expect(result.success).toBe(true)
expect(result.data).toMatchObject(productData)
})
it('handles validation errors', async () => {
const invalidData = { name: '', sku: '', price: -1 }
const result = await productService.create(invalidData)
expect(result.success).toBe(false)
expect(result.error).toBeDefined()
})
})
})
API Route Testing
// __tests__/api/products.test.ts
import { createMocks } from 'node-mocks-http'
import handler from '@/app/api/products/route'
describe('/api/products', () => {
it('GET returns products list', async () => {
const { req, res } = createMocks({
method: 'GET',
query: { page: '1', limit: '10' }
})
await handler(req, res)
expect(res._getStatusCode()).toBe(200)
const data = JSON.parse(res._getData())
expect(data.data).toBeDefined()
expect(Array.isArray(data.data)).toBe(true)
})
it('POST creates new product', async () => {
const productData = {
name: 'New Product',
sku: 'NP-001',
price: 49.99
}
const { req, res } = createMocks({
method: 'POST',
body: productData
})
await handler(req, res)
expect(res._getStatusCode()).toBe(201)
const data = JSON.parse(res._getData())
expect(data.data).toMatchObject(productData)
})
})
Test Coverage Requirements
Minimum Coverage Targets
- Unit Tests: All utility functions and business logic (90%+)
- Component Tests: All React components (85%+)
- Integration Tests: API routes and database operations (80%+)
- E2E Tests: Critical user workflows (70%+)
Coverage Reports
# Generate coverage report
pnpm test:coverage
# View coverage in browser
pnpm test:coverage:open
Test Utilities
Mock Data
// lib/test-utils/mocks.ts
export const mockProduct: Product = {
id: '123',
name: 'Test Product',
sku: 'TEST-001',
price: 29.99,
cost_price: 15.00,
category_id: 'cat-123',
is_active: true,
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z'
}
export const mockInventory: Inventory = {
id: '456',
product_id: '123',
warehouse_id: 'wh-123',
quantity_on_hand: 100,
quantity_allocated: 10,
quantity_available: 90,
reorder_point: 20,
max_stock_level: 500,
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z'
}
export const mockUser: User = {
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
role: 'admin',
is_active: true,
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z'
}
Test Helpers
// lib/test-utils/helpers.ts
import { render, RenderOptions } from '@testing-library/react'
import { ReactElement } from 'react'
import { ThemeProvider } from '@/components/providers/theme'
const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
return (
<ThemeProvider>
{children}
</ThemeProvider>
)
}
const customRender = (
ui: ReactElement,
options?: Omit<RenderOptions, 'wrapper'>
) => render(ui, { wrapper: AllTheProviders, ...options })
export * from '@testing-library/react'
export { customRender as render }
Testing Commands
# Run all tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run tests with coverage
pnpm test:coverage
# Run specific test file
pnpm test products.test.ts
# Run tests matching pattern
pnpm test --testNamePattern="create product"
# Run E2E tests
pnpm test:e2e
Best Practices
Test Organization
- Group related tests using
describeblocks - Use descriptive test names that explain what is being tested
- Follow the Arrange-Act-Assert pattern
- Keep tests focused on a single behavior
Mocking Guidelines
- Mock external dependencies (APIs, databases)
- Use real implementations for internal utilities when possible
- Reset mocks between tests
- Mock at the appropriate level (module vs function)
Async Testing
// Testing async operations
describe('async operations', () => {
it('handles async data fetching', async () => {
const promise = fetchUserData('123')
await expect(promise).resolves.toMatchObject({
id: '123',
name: expect.any(String)
})
})
it('handles async errors', async () => {
const promise = fetchUserData('invalid-id')
await expect(promise).rejects.toThrow('User not found')
})
})
Testing Hooks
// Testing custom hooks
import { renderHook, act } from '@testing-library/react'
import { useProducts } from '@/hooks/use-products'
describe('useProducts', () => {
it('fetches products on mount', async () => {
const { result } = renderHook(() => useProducts({ page: 1 }))
expect(result.current.loading).toBe(true)
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0))
})
expect(result.current.loading).toBe(false)
expect(result.current.products).toHaveLength(10)
})
})