Testing
When to Use
Every design system component. Tests in a design system protect the API contract — they catch regressions before consumers notice.
Decision
| If you need... | Use... | Why |
|---|---|---|
| Behavior/interaction tests | React Testing Library (RTL) | Tests from user perspective; survives refactoring |
| Automated accessibility checks | jest-axe or vitest-axe | axe-core runs on rendered output; catches WCAG violations per component |
| Visual regression | Storybook + Chromatic or Playwright visual | Pixel-level comparison; catches unintended style changes |
| Type-level tests | expectTypeOf (vitest) or tsd |
Verify TypeScript inference works correctly for polymorphic props |
| Component isolation | RTL render() without full app setup |
No routing/store needed; fast; focused |
Pattern
RTL + jest-axe (standard design system test):
import { render, screen, fireEvent } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
import { Button } from './Button';
expect.extend(toHaveNoViolations);
describe('Button', () => {
it('renders with correct variant class', () => {
render(<Button variant="primary">Click me</Button>);
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
});
it('calls onClick when clicked', async () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledOnce();
});
it('has no accessibility violations', async () => {
const { container } = render(<Button>Accessible</Button>);
expect(await axe(container)).toHaveNoViolations();
});
it('is disabled when disabled prop is set', () => {
render(<Button disabled>Disabled</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
});
Common Mistakes
- Testing implementation details (class names, internal state) → brittle; breaks on refactoring; test behavior via
getByRole, notquerySelector('.btn-primary') - Skipping axe tests → accessibility regressions are silent; automated axe catches ~35% of WCAG violations; worth running on every component
- Testing only the happy path → design system components must handle disabled, loading, error states; test all variant combinations that have different behavior
- Using
getByTestIdas default selector → couples tests todata-testidattributes; prefer semantic queries (getByRole,getByLabelText) that reflect how users interact - No visual regression baseline → style changes are invisible in unit tests; set up Storybook + Chromatic or Playwright screenshots for CSS-impacting changes
- Mocking child components in integration tests → defeats the purpose of testing composition; only mock network/external dependencies
See Also
- Performance
- Best Practices and Anti-Patterns
- Reference: React Testing Library
- Reference: jest-axe
- Reference: Storybook accessibility testing