# Test Levels Framework Comprehensive guide for determining appropriate test levels (unit, integration, E2E) for different scenarios. ## Test Level Decision Matrix ### Unit Tests **When to use:** - Testing pure functions and business logic - Algorithm correctness - Input validation and data transformation - Error handling in isolated components - Complex calculations or state machines **Characteristics:** - Fast execution (immediate feedback) - No external dependencies (DB, API, file system) - Highly maintainable and stable - Easy to debug failures **Example scenarios:** ```yaml unit_test: component: 'PriceCalculator' scenario: 'Calculate discount with multiple rules' justification: 'Complex business logic with multiple branches' mock_requirements: 'None - pure function' ``` ### Integration Tests **When to use:** - Component interaction verification - Database operations and transactions - API endpoint contracts - Service-to-service communication - Middleware and interceptor behavior **Characteristics:** - Moderate execution time - Tests component boundaries - May use test databases or containers - Validates system integration points **Example scenarios:** ```yaml integration_test: components: ['UserService', 'AuthRepository'] scenario: 'Create user with role assignment' justification: 'Critical data flow between service and persistence' test_environment: 'In-memory database' ``` ### End-to-End Tests **When to use:** - Critical user journeys - Cross-system workflows - Visual regression testing - Compliance and regulatory requirements - Final validation before release **Characteristics:** - Slower execution - Tests complete workflows - Requires full environment setup - Most realistic but most brittle **Example scenarios:** ```yaml e2e_test: journey: 'Complete checkout process' scenario: 'User purchases with saved payment method' justification: 'Revenue-critical path requiring full validation' environment: 'Staging with test payment gateway' ``` ## Test Level Selection Rules ### Favor Unit Tests When: - Logic can be isolated - No side effects involved - Fast feedback needed - High cyclomatic complexity ### Favor Integration Tests When: - Testing persistence layer - Validating service contracts - Testing middleware/interceptors - Component boundaries critical ### Favor E2E Tests When: - User-facing critical paths - Multi-system interactions - Regulatory compliance scenarios - Visual regression important ## Anti-patterns to Avoid - E2E testing for business logic validation - Unit testing framework behavior - Integration testing third-party libraries - Duplicate coverage across levels ## Duplicate Coverage Guard **Before adding any test, check:** 1. Is this already tested at a lower level? 2. Can a unit test cover this instead of integration? 3. Can an integration test cover this instead of E2E? **Coverage overlap is only acceptable when:** - Testing different aspects (unit: logic, integration: interaction, e2e: user experience) - Critical paths requiring defense in depth - Regression prevention for previously broken functionality ## Test Naming Conventions - Unit: `test_{component}_{scenario}` - Integration: `test_{flow}_{interaction}` - E2E: `test_{journey}_{outcome}` ## Test ID Format `{EPIC}.{STORY}-{LEVEL}-{SEQ}` Examples: - `1.3-UNIT-001` - `1.3-INT-002` - `1.3-E2E-001` ## Real Code Examples ### Example 1: E2E Test (Full User Journey) **Scenario**: User logs in, navigates to dashboard, and places an order. ```typescript // tests/e2e/checkout-flow.spec.ts import { test, expect } from '@playwright/test'; import { createUser, createProduct } from '../test-utils/factories'; test.describe('Checkout Flow', () => { test('user can complete purchase with saved payment method', async ({ page, apiRequest }) => { // Setup: Seed data via API (fast!) const user = createUser({ email: 'buyer@example.com', hasSavedCard: true }); const product = createProduct({ name: 'Widget', price: 29.99, stock: 10 }); await apiRequest.post('/api/users', { data: user }); await apiRequest.post('/api/products', { data: product }); // Network-first: Intercept BEFORE action const loginPromise = page.waitForResponse('**/api/auth/login'); const cartPromise = page.waitForResponse('**/api/cart'); const orderPromise = page.waitForResponse('**/api/orders'); // Step 1: Login await page.goto('/login'); await page.fill('[data-testid="email"]', user.email); await page.fill('[data-testid="password"]', 'password123'); await page.click('[data-testid="login-button"]'); await loginPromise; // Assert: Dashboard visible await expect(page).toHaveURL('/dashboard'); await expect(page.getByText(`Welcome, ${user.name}`)).toBeVisible(); // Step 2: Add product to cart await page.goto(`/products/${product.id}`); await page.click('[data-testid="add-to-cart"]'); await cartPromise; await expect(page.getByText('Added to cart')).toBeVisible(); // Step 3: Checkout with saved payment await page.goto('/checkout'); await expect(page.getByText('Visa ending in 1234')).toBeVisible(); // Saved card await page.click('[data-testid="use-saved-card"]'); await page.click('[data-testid="place-order"]'); await orderPromise; // Assert: Order confirmation await expect(page.getByText('Order Confirmed')).toBeVisible(); await expect(page.getByText(/Order #\d+/)).toBeVisible(); await expect(page.getByText('$29.99')).toBeVisible(); }); }); ``` **Key Points (E2E)**: - Tests complete user journey across multiple pages - API setup for data (fast), UI for assertions (user-centric) - Network-first interception to prevent flakiness - Validates critical revenue path end-to-end ### Example 2: Integration Test (API/Service Layer) **Scenario**: UserService creates user and assigns role via AuthRepository. ```typescript // tests/integration/user-service.spec.ts import { test, expect } from '@playwright/test'; import { createUser } from '../test-utils/factories'; test.describe('UserService Integration', () => { test('should create user with admin role via API', async ({ request }) => { const userData = createUser({ role: 'admin' }); // Direct API call (no UI) const response = await request.post('/api/users', { data: userData, }); expect(response.status()).toBe(201); const createdUser = await response.json(); expect(createdUser.id).toBeTruthy(); expect(createdUser.email).toBe(userData.email); expect(createdUser.role).toBe('admin'); // Verify database state const getResponse = await request.get(`/api/users/${createdUser.id}`); expect(getResponse.status()).toBe(200); const fetchedUser = await getResponse.json(); expect(fetchedUser.role).toBe('admin'); expect(fetchedUser.permissions).toContain('user:delete'); expect(fetchedUser.permissions).toContain('user:update'); // Cleanup await request.delete(`/api/users/${createdUser.id}`); }); test('should validate email uniqueness constraint', async ({ request }) => { const userData = createUser({ email: 'duplicate@example.com' }); // Create first user const response1 = await request.post('/api/users', { data: userData }); expect(response1.status()).toBe(201); const user1 = await response1.json(); // Attempt duplicate email const response2 = await request.post('/api/users', { data: userData }); expect(response2.status()).toBe(409); // Conflict const error = await response2.json(); expect(error.message).toContain('Email already exists'); // Cleanup await request.delete(`/api/users/${user1.id}`); }); }); ``` **Key Points (Integration)**: - Tests service layer + database interaction - No UI involved—pure API validation - Business logic focus (role assignment, constraints) - Faster than E2E, more realistic than unit tests ### Example 3: Component Test (Isolated UI Component) **Scenario**: Test button component in isolation with props and user interactions. ```typescript // src/components/Button.cy.tsx (Cypress Component Test) import { Button } from './Button'; describe('Button Component', () => { it('should render with correct label', () => { cy.mount(