# QA Best Practices for React and Next.js Guidelines for writing maintainable tests, debugging failures, and measuring test quality. --- ## Table of Contents - [Writing Testable Code](#writing-testable-code) - [Test Naming Conventions](#test-naming-conventions) - [Arrange-Act-Assert Pattern](#arrange-act-assert-pattern) - [Test Isolation Principles](#test-isolation-principles) - [Handling Flaky Tests](#handling-flaky-tests) - [Code Review for Testability](#code-review-for-testability) - [Test Maintenance Strategies](#test-maintenance-strategies) - [Debugging Failed Tests](#debugging-failed-tests) - [Quality Metrics and KPIs](#quality-metrics-and-kpis) --- ## Writing Testable Code Testable code is easy to understand, has clear boundaries, and minimizes dependencies. ### Dependency Injection Instead of creating dependencies inside functions, pass them as parameters. **Hard to Test:** ```typescript // src/services/userService.ts import { prisma } from '../lib/prisma'; import { sendEmail } from '../lib/email'; export async function createUser(data: UserInput) { const user = await prisma.user.create({ data }); await sendEmail(user.email, 'Welcome!'); return user; } ``` **Easy to Test:** ```typescript // src/services/userService.ts export function createUserService( db: PrismaClient, emailService: EmailService ) { return { async createUser(data: UserInput) { const user = await db.user.create({ data }); await emailService.send(user.email, 'Welcome!'); return user; }, }; } // Usage in app const userService = createUserService(prisma, emailService); // Usage in tests const mockDb = { user: { create: jest.fn() } }; const mockEmail = { send: jest.fn() }; const testService = createUserService(mockDb, mockEmail); ``` ### Pure Functions Pure functions are deterministic and have no side effects, making them trivial to test. **Impure (Hard to Test):** ```typescript function formatTimestamp() { const now = new Date(); return `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`; } ``` **Pure (Easy to Test):** ```typescript function formatTimestamp(date: Date): string { return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; } // Test expect(formatTimestamp(new Date('2024-03-15'))).toBe('2024-3-15'); ``` ### Separation of Concerns Separate business logic from UI and I/O operations. **Mixed Concerns (Hard to Test):** ```typescript // Component with embedded business logic function CheckoutForm() { const [total, setTotal] = useState(0); const handleSubmit = async (items: CartItem[]) => { // Business logic mixed with UI let sum = 0; for (const item of items) { sum += item.price * item.quantity; if (item.category === 'electronics') { sum *= 0.9; // 10% discount } } const tax = sum * 0.08; const finalTotal = sum + tax; // API call await fetch('/api/orders', { method: 'POST', body: JSON.stringify({ items, total: finalTotal }), }); setTotal(finalTotal); }; return
...
; } ``` **Separated Concerns (Easy to Test):** ```typescript // Pure business logic (easy to unit test) export function calculateOrderTotal(items: CartItem[]): number { return items.reduce((sum, item) => { const subtotal = item.price * item.quantity; const discount = item.category === 'electronics' ? 0.9 : 1; return sum + subtotal * discount; }, 0); } export function calculateTax(subtotal: number, rate = 0.08): number { return subtotal * rate; } // Custom hook for order logic (testable with renderHook) export function useCheckout() { const [total, setTotal] = useState(0); const mutation = useMutation(createOrder); const checkout = async (items: CartItem[]) => { const subtotal = calculateOrderTotal(items); const tax = calculateTax(subtotal); const finalTotal = subtotal + tax; await mutation.mutateAsync({ items, total: finalTotal }); setTotal(finalTotal); }; return { checkout, total, isLoading: mutation.isLoading }; } // Component (integration testable) function CheckoutForm() { const { checkout, total, isLoading } = useCheckout(); return
checkout(items)}>...
; } ``` ### Component Design for Testability | Pattern | Testability | Example | |---------|-------------|---------| | Props over context | High | `