React Test Writing
Stack
Vitest + React Testing Library + MSW (Mock Service Worker) + happy-dom
File Conventions
- •Test files go in
react-app/__tests__/namedComponentName.test.tsx - •MSW handlers live in
react-app/__tests__/mocks/handlers.ts - •Config:
react-app/vitest.config.ts - •Run:
cd react-app && npx vitest
Key Utilities
Import from react-app/__tests__/testutils.tsx:
tsx
import { renderWithProviders, createTestStore } from "./testutils";
- •
renderWithProviders(ui, { preloadedState })-- wraps component in Redux Provider + MemoryRouter - •
createTestStore(preloadedState)-- creates a store with RTK Query middleware
Authenticated state for protected components:
tsx
const authenticatedState = {
user: { access: "fake-token", refresh: "fake-refresh", username: "testuser" },
};
MSW Mocking
Default handlers in __tests__/mocks/handlers.ts cover common endpoints (accounts, assets, asset-info, quote, fred-data, token, restaurants). For component-specific endpoints not already mocked, add per-test overrides:
tsx
import { server } from "./mocks/server";
import { http } from "msw";
server.use(
http.get("http://localhost:8000/api/my-endpoint/", () => {
return Response.json({ key: "value" }, { status: 200 });
})
);
Use import.meta.env.VITE_APP_DJANGO_PORTFOLIO_URL (and similar) for base URLs in handlers.
Test Structure
tsx
import { describe, it, expect } from "vitest";
import { screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "./testutils";
import MyComponent from "../src/components/MyComponent";
describe("MyComponent", () => {
it("renders data after loading", async () => {
renderWithProviders(<MyComponent />, { preloadedState: authenticatedState });
// RTK Query triggers a fetch -- wait for async data
expect(await screen.findByText("Expected Text")).toBeInTheDocument();
});
it("handles user interaction", async () => {
const user = userEvent.setup();
renderWithProviders(<MyComponent />);
await user.click(screen.getByRole("button", { name: /submit/i }));
await waitFor(() => {
expect(screen.getByText("Success")).toBeInTheDocument();
});
});
});
Workflow
- •Read the component -- identify which RTK Query hooks it uses and what props it needs
- •Check handlers.ts -- verify the endpoints are mocked; add
server.use()overrides if not - •Write tests:
- •Render with
renderWithProvidersand appropriatepreloadedState - •Assert loading state (spinner/text) appears first
- •Use
findByText/waitForfor async data assertions - •Use
userEventfor interactions (clicks, typing, form submission) - •Test error states by overriding MSW handlers to return error responses
- •Render with
- •Run --
cd react-app && npx vitest --runto execute once, ornpx vitestfor watch mode