V0 is now available! We'll release moreeee soooon .
Writing tests for React apps doesn't have to be complex. A modern strategy involves a combination of unit, integration, and end-to-end (E2E) testing β each serving a distinct purpose.
This post outlines a simple but effective approach to testing React apps in 2025 using tools like Vitest, Testing Library, and Playwright.
Use unit tests for pure functions, hooks, and small components without side effects.
Tool: vitest
Goal: Fast feedback on isolated logic
// hooks/useCounter.ts
export function useCounter() {
const [count, setCount] = useState(0);
const increment = () => setCount((c) => c + 1);
return { count, increment };
}
// hooks/useCounter.test.ts
import { renderHook, act } from "@testing-library/react";
import { useCounter } from "./useCounter";
test("increments count", () => {
const { result } = renderHook(() => useCounter());
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
β Keep unit tests pure and isolated. Avoid DOM or network unless mocked.
Test UI + logic + interactions, but keep external dependencies mocked.
Tool: @testing-library/react
+ msw
(for mocking API)
Goal: Ensure components render and behave correctly with real user interactions.
// components/UserList.tsx
export function UserList() {
const { data } = useQuery(["users"], fetchUsers);
return (
<ul>
{data?.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// components/UserList.test.tsx
import { render, screen } from "@testing-library/react";
import { rest } from "msw";
import { setupServer } from "msw/node";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { UserList } from "./UserList";
const server = setupServer(
rest.get("/api/users", (_, res, ctx) =>
res(ctx.json([{ id: 1, name: "Alice" }]))
)
);
beforeAll(() => server.listen());
afterEach(() => server
β Focus on behavior, not implementation details. Simulate user flows, not component internals.
E2E tests ensure everything works from a real userβs perspective: the app, APIs, routing, styles, etc.
Tool: playwright
Goal: Catch real-world issues before users do.
// e2e/home.spec.ts
import { test, expect } from "@playwright/test";
test("homepage has welcome text", async ({ page }) => {
await page.goto("http://localhost:3000");
await expect(page.getByText("Welcome")).toBeVisible();
});
β Write fewer E2E tests (only critical paths). They are slower, but valuable.
Layer | Tooling | Use case |
---|---|---|
Unit | vitest | Pure functions, hooks |
Integration | Testing Library , msw | Components, user interactions |
E2E | playwright | Full app, routing, backend |
A well-tested React app balances speed, reliability, and realism.
Donβt test everything β test what matters.
If a test gives you confidence your feature works and wonβt break, it's a good test.
Happy testing π