frontend-testing-patterns
Frontend Testing Patterns
This skill provides patterns for testing React applications using Vitest and React Testing Library.
Canonical Examples
Study these real implementations:
- Component Test: task-template-card.test.tsx
- Hook Test: use-task-templates.test.tsx
Testing Pyramid
E2E Tests (Few) ← Playwright (critical user flows)
Integration Tests (Some) ← React Testing Library (component + hooks)
Unit Tests (Many) ← Vitest (utilities, helpers)
Component Testing
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { TaskCard } from './task-card';
describe('TaskCard', () => {
it('renders task name', () => {
const task = { uid: '1', name: 'Test Task', status: 'pending' };
render(<TaskCard task={task} />);
expect(screen.getByText('Test Task')).toBeInTheDocument();
});
it('calls onEdit when edit button is clicked', async () => {
const onEdit = vi.fn();
const task = { uid: '1', name: 'Test Task', status: 'pending' };
render(<TaskCard task={task} onEdit={onEdit} />);
await userEvent.click(screen.getByRole('button', { name: /edit/i }));
expect(onEdit).toHaveBeenCalledWith(task);
});
});
Key Points:
- ✅ Use
screenqueries (not destructuredgetBy*) - ✅ Test user behavior, not implementation
- ✅ Use
userEventfor interactions (notfireEvent)
Hook Testing
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { describe, it, expect } from 'vitest';
import { useTaskTemplates } from './use-task-templates';
function createWrapper() {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
return ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
}
describe('useTaskTemplates', () => {
it('fetches task templates', async () => {
const { result } = renderHook(() => useTaskTemplates('studio_123'), {
wrapper: createWrapper(),
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toHaveLength(3);
});
});
API Mocking (MSW)
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
http.get('/api/tasks', () => {
return HttpResponse.json({
data: [{ uid: '1', name: 'Task 1' }],
meta: { total: 1 },
});
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
What to Test
✅ DO Test
- User interactions (clicks, typing, form submissions)
- Conditional rendering (loading, error, empty states)
- Accessibility (ARIA labels, keyboard navigation)
- Integration with hooks and context
- Edge cases and error scenarios
- Route search-param behavior (page, filters, date) when URL state drives data fetching
❌ DON'T Test
- Implementation details (state variables, function names)
- Third-party library internals
- Styling (use visual regression tests instead)
- Trivial code (getters, setters)
Best Practices Checklist
- Component tests use React Testing Library
- Hook tests use
renderHookwith proper wrappers - API calls mocked with MSW
- Tests focus on user behavior, not implementation
- Accessibility tested (ARIA, keyboard navigation)
- Loading/error/empty states tested
- User interactions use
userEvent(notfireEvent) - Async operations use
waitFororfindBy*queries - Refactor parity tests cover route-state behavior (pagination clamp timing, date/query defaults, and navigation callbacks)
Refactor Parity Suite (Route Decomposition)
When decomposing a large route component, add or update tests that confirm no behavioral regression:
- Loading/empty/data state rendering still matches previous behavior.
- Search-param-driven behavior is preserved (
page,limit,date, filter params). - Pagination clamping happens only after data is available, not during initial loading.
- Route actions (previous/next page, date navigation) still update URL state correctly.
Related Skills
- frontend-ui-components - Component patterns
- frontend-api-layer - API integration
More from allenlin90/eridu-services
service-pattern-nestjs
Comprehensive NestJS service implementation patterns. This skill should be used when implementing Model Services, Orchestration Services, or business logic with NestJS decorators.
8erify-authorization
Patterns for implementing authorization in erify_api with current StudioMembership + AdminGuard behavior, plus planned RBAC references
6data-validation
Provides comprehensive guidance for input validation, data serialization, and ID management in backend APIs. This skill should be used when designing validation schemas, transforming request/response data, mapping database IDs to external identifiers, and ensuring type safety across API boundaries.
6code-quality
Provides general code quality and best practices guidance applicable across languages and frameworks. Focuses on linting, testing, and type safety.
6repository-pattern-nestjs
Comprehensive Prisma repository implementation patterns for NestJS. This skill should be used when implementing repositories that extend BaseRepository or use Prisma delegates.
6task-template-builder
Provides guidelines for the Task Template Builder architecture, including Schema alignment, Draft storage, Drag-and-Drop, and Validation logic.
6