component-testing
Comprehensive testing patterns for React components with MSW, React Query, and Vitest. Use when writing tests, setting up mocks, testing API interactions, or ensuring 80% coverage. Keywords - testing, MSW, React Query, Vitest, mocks, coverage, API testing.
When & Why to Use This Skill
This Claude skill provides a comprehensive framework for React component testing using Vitest, MSW (Mock Service Worker), and TanStack Query. It establishes standardized patterns for API mocking, user interaction simulation, and context provider testing, enabling developers to achieve high-quality code with a minimum of 80% test coverage through consistent and maintainable testing practices.
Use Cases
- Mocking REST API interactions with MSW 2.x to simulate various server responses, including success, 400 validation errors, and 500 server failures.
- Testing complex React components that rely on React Query for data fetching, ensuring proper handling of loading skeletons and error alerts.
- Simulating realistic user interactions using React Testing Library and user-event to verify UI behavior and state changes.
- Setting up standardized test environments by wrapping components in necessary providers like QueryClientProvider and custom UI providers.
- Ensuring high code quality and coverage by implementing tests for edge cases, accessibility features, and form validation logic.
| name | component-testing |
|---|---|
| description | Comprehensive testing patterns for React components with MSW, React Query, and Vitest. Use when writing tests, setting up mocks, testing API interactions, or ensuring 80% coverage. Keywords - testing, MSW, React Query, Vitest, mocks, coverage, API testing. |
| compatibility | Designed for Vitest with React Testing Library, MSW 2.x, React Query v5 |
| version | "2.0.0" |
| author | jpmorgan-payments |
| lastUpdated | "2025-12-24" |
| priority | critical |
Component Testing Patterns
Overview
All components require comprehensive tests with minimum 80% coverage. Tests must be colocated with implementation files (not in separate __tests__/ directories).
Test File Requirements
File naming: ComponentName.test.tsx next to ComponentName.tsx
Basic structure:
import { render, screen, waitFor } from "@testing-library/react";
import { userEvent } from "@test-utils";
import { http, HttpResponse } from "msw";
import { server } from "@/msw/server";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { EBComponentsProvider } from "@/providers/EBComponentsProvider";
// Setup QueryClient for tests
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});
// Mock data setup
const mockData = {
// Component-specific mock data
};
Test Setup Helper Function
Always create a renderComponent helper:
const renderComponent = (props = {}) => {
// Reset MSW handlers before each render
server.resetHandlers();
// Setup explicit API mock handlers
server.use(
http.get("/api/endpoint", () => {
return HttpResponse.json(mockData);
})
);
return render(
<EBComponentsProvider
apiBaseUrl="/"
headers={{}}
contentTokens={{ name: "enUS" }}
>
<QueryClientProvider client={queryClient}>
<ComponentName {...props} />
</QueryClientProvider>
</EBComponentsProvider>
);
};
Test Structure
describe("ComponentName", () => {
// Mock external dependencies
vi.mock("@/components/ui/someComponent", () => ({
useSomeHook: () => ({ someFunction: vi.fn() }),
}));
test("renders correctly with initial data", async () => {
renderComponent();
// Wait for async operations
await waitFor(() => {
expect(screen.getByText(/expected text/i)).toBeInTheDocument();
});
});
test("handles user interactions", async () => {
renderComponent();
// Simulate user actions
await userEvent.click(screen.getByRole("button", { name: /action/i }));
// Verify state changes
expect(screen.getByText(/new state/i)).toBeInTheDocument();
});
test("handles API interactions", async () => {
// Setup specific API mock for this test
server.use(
http.post("/api/endpoint", () => {
return HttpResponse.json({ success: true });
})
);
renderComponent();
// Trigger API call
await userEvent.click(screen.getByRole("button", { name: /submit/i }));
// Verify API interaction results
await waitFor(() => {
expect(screen.getByText(/success/i)).toBeInTheDocument();
});
});
});
MSW API Mocking Patterns
// Setup mock handlers
server.use(
// GET requests
http.get("/api/resource/:id", ({ params }) => {
return HttpResponse.json(mockData[params.id]);
}),
// POST requests with response validation
http.post("/api/resource", async ({ request }) => {
const body = await request.json();
if (!isValidData(body)) {
return HttpResponse.json(
{ error: "Invalid data" },
{ status: 400 }
);
}
return HttpResponse.json({ success: true });
}),
// Error scenarios
http.get("/api/error-case", () => {
return HttpResponse.json(
{ error: "Something went wrong" },
{ status: 500 }
);
})
);
Context and Provider Testing
// For components using multiple contexts
const renderWithProviders = (ui: React.ReactElement) => {
return render(
<EBComponentsProvider apiBaseUrl="/" headers={{}}>
<FeatureContextProvider value={mockFeatureContext}>
<QueryClientProvider client={queryClient}>
{ui}
</QueryClientProvider>
</FeatureContextProvider>
</EBComponentsProvider>
);
};
// Testing context updates
test("responds to context changes", async () => {
const { rerender } = renderWithProviders(<ComponentName />);
// Verify initial state
expect(screen.getByText(/initial/i)).toBeInTheDocument();
// Rerender with new context
rerender(
<FeatureContextProvider value={newMockContext}>
<ComponentName />
</FeatureContextProvider>
);
// Verify updated state
expect(screen.getByText(/updated/i)).toBeInTheDocument();
});
Testing Best Practices
- Test component rendering with different prop combinations
- Verify user interactions and their effects
- Test error states and loading states
- Ensure proper API interaction handling
- Test accessibility features (ARIA, keyboard nav)
- Cover edge cases and validation scenarios
Test Coverage Requirements
- ✅ Minimum 80% line coverage
- ✅ Cover all user interaction paths
- ✅ Test all API integration points
- ✅ Verify error handling and edge cases
- ✅ Test accessibility features
- ✅ Include integration tests for complex flows
Query Key Management
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { getSmbdoGetClientQueryKey } from "@/api/generated/smbdo";
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (data) => api.post("/endpoint", data),
onSuccess: (response) => {
// Invalidate related queries
queryClient.invalidateQueries({
queryKey: getSmbdoGetClientQueryKey(clientId),
});
},
});
Common Patterns
Testing Forms
test("validates form inputs", async () => {
renderComponent();
const input = screen.getByLabelText(/email/i);
await userEvent.type(input, "invalid-email");
await userEvent.click(screen.getByRole("button", { name: /submit/i }));
expect(screen.getByText(/invalid email/i)).toBeInTheDocument();
});
Testing Loading States
Use skeleton components, never "Loading..." text:
test("shows loading state", async () => {
renderComponent();
// Check for skeleton loader
expect(screen.getByTestId("skeleton")).toBeInTheDocument();
// Wait for data to load
await waitFor(() => {
expect(screen.queryByTestId("skeleton")).not.toBeInTheDocument();
});
});
Testing Error States
Always use ServerErrorAlert component:
test("displays error alert", async () => {
server.use(
http.get("/api/endpoint", () => {
return HttpResponse.json(
{ error: "Server error" },
{ status: 500 }
);
})
);
renderComponent();
await waitFor(() => {
expect(screen.getByText(/server error/i)).toBeInTheDocument();
expect(screen.getByRole("button", { name: /try again/i })).toBeInTheDocument();
});
});
Running Tests
After ANY code change, you MUST:
cd embedded-components
yarn test
This runs: typecheck → format:check → lint → test:unit
Quick fix commands:
cd embedded-components
yarn format # Auto-fix formatting
yarn lint:fix # Auto-fix linting
yarn typecheck # Check types only
yarn test:unit # Run tests only
Never Commit Code With
- ❌ TypeScript errors
- ❌ Formatting errors (Prettier)
- ❌ Linting errors
- ❌ Failing tests
References
- See
embedded-components/ARCHITECTURE.mdfor architecture patterns - See
.github/copilot/prompts/run-tests-and-fix.mdfor testing workflows