--- name: testing-strategy description: Comprehensive testing strategy using Vitest for unit/integration tests and Playwright for E2E tests with best practices and coverage targets version: 1.0.0 author: AI-Vibe-Prompts tags: [testing, vitest, playwright, quality, tdd, e2e] auto_invoke: true --- # Testing Strategy Skill ## Objective Implement comprehensive testing strategy covering unit, integration, and E2E tests using modern tools (Vitest, Playwright) with clear coverage targets and best practices. ## When to Use This Skill Auto-invoke when: - User mentions "test", "testing", "coverage", "TDD", "E2E" - Setting up new project - Adding new features (need tests) - Debugging test failures - Improving test coverage ## Testing Pyramid ``` /\ /E2E\ Few, slow, expensive /------\ / Integ \ Some, moderate speed /----------\ / Unit Tests \ Many, fast, cheap /--------------\ ``` **Distribution**: - **70%** Unit Tests - Fast, isolated, cheap - **20%** Integration Tests - Moderate speed, test interactions - **10%** E2E Tests - Slow, expensive, critical user flows ## Test Types ### 1. Unit Tests (Vitest) **What**: Test individual functions/components in isolation **Tools**: Vitest, React Testing Library **Coverage Target**: 80%+ **Setup**: ```bash npm install -D vitest @vitest/ui @testing-library/react @testing-library/jest-dom ``` **Config** (`vitest.config.ts`): ```typescript import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], test: { globals: true, environment: 'jsdom', setupFiles: './tests/setup.ts', coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], exclude: ['node_modules/', 'tests/'], thresholds: { lines: 80, functions: 80, branches: 75, statements: 80 } } } }) ``` **Example** (`Button.test.tsx`): ```typescript import { render, screen, fireEvent } from '@testing-library/react' import { describe, it, expect, vi } from 'vitest' import { Button } from './Button' describe('Button', () => { it('renders with text', () => { render() expect(screen.getByText('Click me')).toBeInTheDocument() }) it('calls onClick when clicked', () => { const handleClick = vi.fn() render() fireEvent.click(screen.getByText('Click')) expect(handleClick).toHaveBeenCalledOnce() }) it('is disabled when disabled prop is true', () => { render() expect(screen.getByRole('button')).toBeDisabled() }) }) ``` **Commands**: ```bash npm run test # Run all tests npm run test:watch # Watch mode npm run test:ui # Visual UI npm run test:coverage # With coverage ``` ### 2. Integration Tests (Vitest) **What**: Test component interactions, API calls, state management **Example** (`UserProfile.test.tsx`): ```typescript import { render, screen, waitFor } from '@testing-library/react' import { describe, it, expect, vi } from 'vitest' import { UserProfile } from './UserProfile' // Mock API vi.mock('./api', () => ({ fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: 'John Doe', email: 'john@example.com' })) })) describe('UserProfile Integration', () => { it('fetches and displays user data', async () => { render() expect(screen.getByText('Loading...')).toBeInTheDocument() await waitFor(() => { expect(screen.getByText('John Doe')).toBeInTheDocument() expect(screen.getByText('john@example.com')).toBeInTheDocument() }) }) }) ``` ### 3. E2E Tests (Playwright) **What**: Test complete user flows in real browser **Tools**: Playwright **Coverage Target**: Critical paths only **Setup**: ```bash npm install -D @playwright/test npx playwright install ``` **Config** (`playwright.config.ts`): ```typescript import { defineConfig, devices } from '@playwright/test' export default defineConfig({ testDir: './e2e', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: 'html', use: { baseURL: 'http://localhost:3000', trace: 'on-first-retry', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, { name: 'mobile', use: { ...devices['iPhone 13'] }, }, ], webServer: { command: 'npm run dev', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, }) ``` **Example** (`e2e/auth.spec.ts`): ```typescript import { test, expect } from '@playwright/test' test.describe('Authentication Flow', () => { test('user can sign up and log in', async ({ page }) => { // Sign up await page.goto('/signup') await page.fill('[name="email"]', 'test@example.com') await page.fill('[name="password"]', 'SecurePass123!') await page.click('button[type="submit"]') // Should redirect to dashboard await expect(page).toHaveURL(/\/dashboard/) await expect(page.locator('h1')).toContainText('Welcome') // Log out await page.click('[aria-label="User menu"]') await page.click('text=Logout') // Should redirect to home await expect(page).toHaveURL('/') // Log back in await page.goto('/login') await page.fill('[name="email"]', 'test@example.com') await page.fill('[name="password"]', 'SecurePass123!') await page.click('button[type="submit"]') await expect(page).toHaveURL(/\/dashboard/) }) }) ``` **Commands**: ```bash npx playwright test # Run all E2E npx playwright test --ui # Interactive mode npx playwright test --headed # Show browser npx playwright test --project=chromium # Specific browser npx playwright show-report # View last report ``` ## Testing Best Practices ### AAA Pattern ```typescript // Arrange const user = { id: 1, name: 'John' } const mockFetch = vi.fn() // Act const result = await fetchUser(mockFetch, 1) // Assert expect(result).toEqual(user) expect(mockFetch).toHaveBeenCalledWith('/api/users/1') ``` ### Test Naming ```typescript // Good: descriptive, explains what and when it('displays error message when API returns 404', () => {}) it('disables submit button when form is invalid', () => {}) // Bad: vague, unclear it('works', () => {}) it('test 1', () => {}) ``` ### One Assertion Per Test (Guideline) ```typescript // Prefer focused tests it('renders user name', () => { render() expect(screen.getByText('John')).toBeInTheDocument() }) it('renders user email', () => { render() expect(screen.getByText('john@example.com')).toBeInTheDocument() }) // Over complex tests it('renders user data', () => { // Multiple unrelated assertions }) ``` ### Mock External Dependencies ```typescript // Mock API calls vi.mock('./api', () => ({ fetchUser: vi.fn() })) // Mock environment vi.stubEnv('API_URL', 'http://test-api.com') // Mock timers vi.useFakeTimers() const now = new Date('2024-01-01') vi.setSystemTime(now) ``` ## Coverage Strategy ### What to Test ✅ **Do Test**: - Business logic - Edge cases and error handling - User interactions - API integration - State management - Validation logic - Critical user flows (E2E) ❌ **Don't Test**: - Third-party libraries - Framework internals - Constants - Simple getters/setters - Generated code ### Coverage Targets **Minimum**: - Lines: 80% - Functions: 80% - Branches: 75% - Statements: 80% **Ideal**: - Critical paths: 100% - Business logic: 95%+ - UI components: 85%+ - Utilities: 90%+ ### Run Coverage ```bash npm run test:coverage # View in browser open coverage/index.html ``` ## Testing Workflow ### 1. TDD Approach (Recommended) ``` 1. Write failing test 2. Write minimal code to pass 3. Refactor 4. Repeat ``` ### 2. Test-After (Pragmatic) ``` 1. Implement feature 2. Write tests 3. Achieve 80%+ coverage 4. Refactor with confidence ``` ### 3. Pre-Commit Testing ```bash # Run before every commit npm run test:quick # Fast unit tests npm run lint npm run typecheck # Run before push npm run test # All unit/integration npm run test:coverage # Verify coverage # Run before deploy npm run test:e2e # Full E2E suite ``` ## Test Organization ### Directory Structure ``` src/ ├── components/ │ ├── Button/ │ │ ├── Button.tsx │ │ ├── Button.test.tsx # Co-located │ │ └── Button.stories.tsx # Storybook │ └── ... tests/ ├── setup.ts # Test setup ├── utils/ # Test utilities │ ├── renderWithProviders.tsx # Custom render │ └── mockData.ts # Test fixtures └── __mocks__/ # Global mocks e2e/ ├── auth.spec.ts ├── checkout.spec.ts └── fixtures/ # E2E test data ``` ### Naming Conventions - Unit/Integration: `*.test.ts` or `*.test.tsx` - E2E: `*.spec.ts` - Setup: `setup.ts`, `vitest.config.ts` ## Continuous Integration ### GitHub Actions Example ```yaml name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 18 cache: 'npm' - run: npm ci - run: npm run lint - run: npm run typecheck - run: npm run test:coverage - name: Upload coverage uses: codecov/codecov-action@v3 with: files: ./coverage/coverage-final.json e2e: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 - run: npm ci - run: npx playwright install --with-deps - run: npm run build - run: npx playwright test - uses: actions/upload-artifact@v3 if: always() with: name: playwright-report path: playwright-report/ ``` ## Debugging Tests ### Vitest ```bash # Run single test file npm run test -- Button.test.tsx # Run tests matching pattern npm run test -- --grep "Button renders" # Debug in VS Code # Add breakpoint, press F5 ``` ### Playwright ```bash # Debug mode npx playwright test --debug # Specific test npx playwright test auth.spec.ts --debug # Trace viewer npx playwright show-trace trace.zip ``` ## Common Testing Patterns ### Testing Async Code ```typescript it('fetches user data', async () => { const { result } = renderHook(() => useUser(1)) await waitFor(() => { expect(result.current.data).toEqual({ id: 1, name: 'John' }) }) }) ``` ### Testing Error States ```typescript it('displays error when fetch fails', async () => { vi.mocked(fetchUser).mockRejectedValue(new Error('Network error')) render() await waitFor(() => { expect(screen.getByText(/error/i)).toBeInTheDocument() }) }) ``` ### Testing Forms ```typescript it('submits form with valid data', async () => { const handleSubmit = vi.fn() render() await userEvent.type(screen.getByLabelText('Email'), 'test@example.com') await userEvent.type(screen.getByLabelText('Password'), 'password123') await userEvent.click(screen.getByRole('button', { name: /submit/i })) expect(handleSubmit).toHaveBeenCalledWith({ email: 'test@example.com', password: 'password123' }) }) ``` ## Integration with Other Skills - `quality-gates` - Run tests as quality check - `git-workflow` - Tests in pre-commit hooks - `codebase-analysis` - Identify untested code ## Package.json Scripts ```json { "scripts": { "test": "vitest", "test:watch": "vitest --watch", "test:ui": "vitest --ui", "test:coverage": "vitest --coverage", "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui", "test:e2e:headed": "playwright test --headed", "test:all": "npm run test:coverage && npm run test:e2e" } } ``` ## Version History - **1.0.0** (2025-01-03): Initial testing strategy with Vitest and Playwright