Frontend Testing
Generate high-quality, comprehensive frontend tests following established conventions and best practices.
When to Apply
Apply this skill when:
- Writing tests for a component, hook, or utility
- Reviewing existing tests for completeness
- Working with Vitest, React Testing Library, or spec files
- Improving test coverage
- Writing unit tests or integration tests for frontend code
Do NOT apply when:
- Testing backend/API code (Python/pytest, Go, etc.)
- Writing E2E tests (Playwright/Cypress)
- Answering conceptual questions without code context
Quick Reference
Tech Stack
| Tool |
Purpose |
| Vitest |
Test runner |
| React Testing Library |
Component testing |
| jsdom |
Test environment |
| nock |
HTTP mocking |
| TypeScript |
Type safety |
Key Commands
# Run all tests
npm test # or: pnpm test / yarn test
# Watch mode
npm run test:watch
# Run specific file
npx vitest path/to/file.spec.tsx
# Generate coverage report
npx vitest --coverage
File Naming
- Test files:
ComponentName.spec.tsx (same directory as component)
- Integration tests:
__tests__/ directory
Test Structure Template
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import Component from './index'
// Mock external dependencies only
vi.mock('@/service/api')
vi.mock('next/navigation', () => ({
useRouter: () => ({ push: vi.fn() }),
usePathname: () => '/test',
}))
describe('ComponentName', () => {
beforeEach(() => {
vi.clearAllMocks()
})
// Rendering tests (REQUIRED)
describe('Rendering', () => {
it('should render without crashing', () => {
render(<Component title="Test" />)
expect(screen.getByText('Test')).toBeInTheDocument()
})
})
// Props tests (REQUIRED)
describe('Props', () => {
it('should apply custom className', () => {
render(<Component className="custom" />)
expect(screen.getByRole('button')).toHaveClass('custom')
})
})
// User Interactions
describe('User Interactions', () => {
it('should handle click events', () => {
const handleClick = vi.fn()
render(<Component />)
fireEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
})
// Edge Cases (REQUIRED)
describe('Edge Cases', () => {
it('should handle null data', () => {
render(<Component data={null} />)
expect(screen.getByText(/no data/i)).toBeInTheDocument()
})
it('should handle empty array', () => {
render(<Component items={[]} />)
expect(screen.getByText(/empty/i)).toBeInTheDocument()
})
})
})
Testing Workflow
Incremental Approach (Required for Multi-File)
NEVER generate all test files at once. For complex components or multi-file directories:
- Analyze & Plan: List all files, order by complexity (simple to complex)
- Process ONE at a time: Write test, run test, fix if needed, then next
- Verify before proceeding: Do NOT continue to next file until current passes
For each file:
1. Write test
2. Run: npx vitest <file>.spec.tsx
3. PASS? -> Mark complete, next file
FAIL? -> Fix first, then continue
Complexity-Based Order
Process in this order for multi-file testing:
- Utility functions (simplest)
- Custom hooks
- Simple components (presentational)
- Medium components (state, effects)
- Complex components (API, routing)
- Integration tests (index files -- last)
When to Refactor First
- Complexity > 50: Break into smaller pieces before testing
- 500+ lines: Consider splitting before testing
- Many dependencies: Extract logic into hooks first
Testing Strategy
Integration Testing First
Prefer integration testing when writing tests for a directory:
- Import real project components directly (including base components and siblings)
- Only mock: API services,
next/navigation, complex context providers
- DO NOT mock base UI components (
Button, Input, Loading, etc.)
- DO NOT mock sibling/child components in the same directory
Path-Level Testing
When assigned to test a directory/path, test ALL content within that path:
- Test all components, hooks, utilities in the directory
- Use incremental approach: one file at a time, verify each before proceeding
- Goal: full coverage of all files in the directory
Core Principles
1. AAA Pattern (Arrange-Act-Assert)
Every test should clearly separate:
- Arrange: Setup test data and render component
- Act: Perform user actions
- Assert: Verify expected outcomes
2. Black-Box Testing
- Test observable behavior, not implementation details
- Use semantic queries (
getByRole, getByLabelText)
- Avoid testing internal state directly
- Prefer pattern matching over hardcoded strings:
// Prefer role-based queries
expect(screen.getByRole('status')).toBeInTheDocument()
// Prefer pattern matching
expect(screen.getByText(/loading/i)).toBeInTheDocument()
3. Single Behavior Per Test
Each test verifies ONE user-observable behavior:
// Good: One behavior
it('should disable button when loading', () => {
render(<Button loading />)
expect(screen.getByRole('button')).toBeDisabled()
})
// Bad: Multiple behaviors
it('should handle loading state', () => {
render(<Button loading />)
expect(screen.getByRole('button')).toBeDisabled()
expect(screen.getByText('Loading...')).toBeInTheDocument()
expect(screen.getByRole('button')).toHaveClass('loading')
})
4. Semantic Naming
Use should <behavior> when <condition>:
it('should show error message when validation fails')
it('should call onSubmit when form is valid')
it('should disable input when isReadOnly is true')
Required Test Scenarios
Always Required (All Components)
- Rendering: Component renders without crashing
- Props: Required props, optional props, default values
- Edge Cases: null, undefined, empty values, boundary conditions
Conditional (When Present)
| Feature |
Test Focus |
useState |
Initial state, transitions, cleanup |
useEffect |
Execution, dependencies, cleanup |
| Event handlers |
All onClick, onChange, onSubmit, keyboard |
| API calls |
Loading, success, error states |
| Routing |
Navigation, params, query strings |
useCallback/useMemo |
Referential equality |
| Context |
Provider values, consumer behavior |
| Forms |
Validation, submission, error display |
Coverage Goals (Per File)
- 100% function coverage
- 100% statement coverage
- >95% branch coverage
- >95% line coverage
References
references/async-testing.md - Async operations, fake timers, and API testing patterns
references/checklist.md - Test generation checklist and validation steps
references/common-patterns.md - Query priority, event handling, forms, modals, lists
references/mocking.md - Mock patterns, Zustand stores, factory functions
references/workflow.md - Incremental testing workflow for multi-file directories
assets/component-test.template.tsx - Component test template
assets/hook-test.template.ts - Hook test template
assets/utility-test.template.ts - Utility function test template
1---2name: frontend-testing3description: Generate comprehensive Vitest and React Testing Library tests for frontend components, hooks, and utilities. Covers test structure, incremental workflow, async patterns, mocking strategies, and coverage goals. Use when writing or reviewing frontend tests, improving coverage, or setting up testing infrastructure for React projects.4license: Sustainable Use License 1.05---67# Frontend Testing89Generate high-quality, comprehensive frontend tests following established conventions and best practices.1011## When to Apply1213Apply this skill when:1415- Writing **tests** for a component, hook, or utility16- Reviewing **existing tests** for completeness17- Working with **Vitest**, **React Testing Library**, or **spec files**18- Improving **test coverage**19- Writing **unit tests** or **integration tests** for frontend code2021**Do NOT apply** when:2223- Testing backend/API code (Python/pytest, Go, etc.)24- Writing E2E tests (Playwright/Cypress)25- Answering conceptual questions without code context2627## Quick Reference2829### Tech Stack3031| Tool | Purpose |32|------|---------|33| Vitest | Test runner |34| React Testing Library | Component testing |35| jsdom | Test environment |36| nock | HTTP mocking |37| TypeScript | Type safety |3839### Key Commands4041```bash42# Run all tests43npm test # or: pnpm test / yarn test4445# Watch mode46npm run test:watch4748# Run specific file49npx vitest path/to/file.spec.tsx5051# Generate coverage report52npx vitest --coverage53```5455### File Naming5657- Test files: `ComponentName.spec.tsx` (same directory as component)58- Integration tests: `__tests__/` directory5960## Test Structure Template6162```typescript63import { render, screen, fireEvent, waitFor } from '@testing-library/react'64import Component from './index'6566// Mock external dependencies only67vi.mock('@/service/api')68vi.mock('next/navigation', () => ({69 useRouter: () => ({ push: vi.fn() }),70 usePathname: () => '/test',71}))7273describe('ComponentName', () => {74 beforeEach(() => {75 vi.clearAllMocks()76 })7778 // Rendering tests (REQUIRED)79 describe('Rendering', () => {80 it('should render without crashing', () => {81 render(<Component title="Test" />)82 expect(screen.getByText('Test')).toBeInTheDocument()83 })84 })8586 // Props tests (REQUIRED)87 describe('Props', () => {88 it('should apply custom className', () => {89 render(<Component className="custom" />)90 expect(screen.getByRole('button')).toHaveClass('custom')91 })92 })9394 // User Interactions95 describe('User Interactions', () => {96 it('should handle click events', () => {97 const handleClick = vi.fn()98 render(<Component onClick={handleClick} />)99 fireEvent.click(screen.getByRole('button'))100 expect(handleClick).toHaveBeenCalledTimes(1)101 })102 })103104 // Edge Cases (REQUIRED)105 describe('Edge Cases', () => {106 it('should handle null data', () => {107 render(<Component data={null} />)108 expect(screen.getByText(/no data/i)).toBeInTheDocument()109 })110111 it('should handle empty array', () => {112 render(<Component items={[]} />)113 expect(screen.getByText(/empty/i)).toBeInTheDocument()114 })115 })116})117```118119## Testing Workflow120121### Incremental Approach (Required for Multi-File)122123**NEVER generate all test files at once.** For complex components or multi-file directories:1241251. **Analyze & Plan**: List all files, order by complexity (simple to complex)1262. **Process ONE at a time**: Write test, run test, fix if needed, then next1273. **Verify before proceeding**: Do NOT continue to next file until current passes128129```130For each file:131 1. Write test132 2. Run: npx vitest <file>.spec.tsx133 3. PASS? -> Mark complete, next file134 FAIL? -> Fix first, then continue135```136137### Complexity-Based Order138139Process in this order for multi-file testing:1401411. Utility functions (simplest)1422. Custom hooks1433. Simple components (presentational)1444. Medium components (state, effects)1455. Complex components (API, routing)1466. Integration tests (index files -- last)147148### When to Refactor First149150- **Complexity > 50**: Break into smaller pieces before testing151- **500+ lines**: Consider splitting before testing152- **Many dependencies**: Extract logic into hooks first153154## Testing Strategy155156### Integration Testing First157158**Prefer integration testing** when writing tests for a directory:159160- Import **real project components** directly (including base components and siblings)161- **Only mock**: API services, `next/navigation`, complex context providers162- **DO NOT mock** base UI components (`Button`, `Input`, `Loading`, etc.)163- **DO NOT mock** sibling/child components in the same directory164165### Path-Level Testing166167When assigned to test a directory/path, test **ALL content** within that path:168169- Test all components, hooks, utilities in the directory170- Use incremental approach: one file at a time, verify each before proceeding171- Goal: full coverage of all files in the directory172173## Core Principles174175### 1. AAA Pattern (Arrange-Act-Assert)176177Every test should clearly separate:178179- **Arrange**: Setup test data and render component180- **Act**: Perform user actions181- **Assert**: Verify expected outcomes182183### 2. Black-Box Testing184185- Test observable behavior, not implementation details186- Use semantic queries (`getByRole`, `getByLabelText`)187- Avoid testing internal state directly188- Prefer pattern matching over hardcoded strings:189190```typescript191// Prefer role-based queries192expect(screen.getByRole('status')).toBeInTheDocument()193194// Prefer pattern matching195expect(screen.getByText(/loading/i)).toBeInTheDocument()196```197198### 3. Single Behavior Per Test199200Each test verifies ONE user-observable behavior:201202```typescript203// Good: One behavior204it('should disable button when loading', () => {205 render(<Button loading />)206 expect(screen.getByRole('button')).toBeDisabled()207})208209// Bad: Multiple behaviors210it('should handle loading state', () => {211 render(<Button loading />)212 expect(screen.getByRole('button')).toBeDisabled()213 expect(screen.getByText('Loading...')).toBeInTheDocument()214 expect(screen.getByRole('button')).toHaveClass('loading')215})216```217218### 4. Semantic Naming219220Use `should <behavior> when <condition>`:221222```typescript223it('should show error message when validation fails')224it('should call onSubmit when form is valid')225it('should disable input when isReadOnly is true')226```227228## Required Test Scenarios229230### Always Required (All Components)2312321. **Rendering**: Component renders without crashing2332. **Props**: Required props, optional props, default values2343. **Edge Cases**: null, undefined, empty values, boundary conditions235236### Conditional (When Present)237238| Feature | Test Focus |239|---------|-----------|240| `useState` | Initial state, transitions, cleanup |241| `useEffect` | Execution, dependencies, cleanup |242| Event handlers | All onClick, onChange, onSubmit, keyboard |243| API calls | Loading, success, error states |244| Routing | Navigation, params, query strings |245| `useCallback`/`useMemo` | Referential equality |246| Context | Provider values, consumer behavior |247| Forms | Validation, submission, error display |248249## Coverage Goals (Per File)250251- 100% function coverage252- 100% statement coverage253- \>95% branch coverage254- \>95% line coverage255256## References257258- `references/async-testing.md` - Async operations, fake timers, and API testing patterns259- `references/checklist.md` - Test generation checklist and validation steps260- `references/common-patterns.md` - Query priority, event handling, forms, modals, lists261- `references/mocking.md` - Mock patterns, Zustand stores, factory functions262- `references/workflow.md` - Incremental testing workflow for multi-file directories263- `assets/component-test.template.tsx` - Component test template264- `assets/hook-test.template.ts` - Hook test template265- `assets/utility-test.template.ts` - Utility function test template