Testing at Google follows the testing pyramid: many unit tests, fewer integration tests, and minimal E2E tests.
The Testing Pyramid
/ E2E \\ ← Few, slow, expensive
/ Integration \\ ← Moderate amount
/ Unit Tests \\ ← Many, fast, cheapUnit Tests with React Testing Library
import { render, screen, fireEvent } from "@testing-library/react";
import { Counter } from "./Counter";
test("increments counter on click", () => {
render(<Counter />);
const button = screen.getByRole("button", { name: /increment/i });
fireEvent.click(button);
expect(screen.getByText("Count: 1")).toBeInTheDocument();
});Testing Hooks
import { renderHook, act } from "@testing-library/react";
import { useCounter } from "./useCounter";
test("useCounter increments", () => {
const { result } = renderHook(() => useCounter());
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});Integration Tests
test("user can search and filter results", async () => {
// Mock API
server.use(
rest.get("/api/products", (req, res, ctx) => {
const q = req.url.searchParams.get("q");
return res(ctx.json(products.filter(p => p.name.includes(q))));
})
);
render(<ProductSearch />);
await userEvent.type(screen.getByRole("searchbox"), "laptop");
await userEvent.click(screen.getByRole("button", { name: /search/i }));
expect(await screen.findAllByRole("listitem")).toHaveLength(3);
});What NOT to Test
- Implementation details (state values, internal methods)
- Third-party library internals
- CSS styling (use visual regression tools instead)
- Constants and configuration
Testing Best Practices
- Test behavior, not implementation
- Use
screen.getByRoleovergetByTestId - Prefer
userEventoverfireEvent(closer to real user behavior) - One assertion per behavior, not per test
- Use MSW for API mocking — it works at the network level