React: From Components to Production · Testing and Accessibility
Component Tests with Vitest and Testing Library
Test components the way users use them.
Vitest (Vite-native, Jest-compatible API) plus React Testing Library is the standard setup. Render components, find elements by role, label or text (getByRole('button', { name: /save/i })), simulate interactions with @testing-library/user-event, and assert on what users see. Avoid testing implementation details such as internal state or CSS classes.
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { Newsletter } from "./Newsletter";
describe("Newsletter", () => {
it("shows a validation error for invalid emails", async () => {
const user = userEvent.setup();
render(<Newsletter />);
await user.type(screen.getByRole("textbox", { name: /email/i }), "not-an-email");
await user.click(screen.getByRole("button", { name: /subscribe/i }));
expect(await screen.findByRole("alert")).toHaveTextContent("Enter a valid email");
});
it("calls onDone after success", async () => {
const onDone = vi.fn();
// ...
});
});
Common pitfalls
- Querying by test IDs or class names when a role or label exists.
- Using
fireEventinstead ofuser-eventfor realistic interactions. - Asserting on snapshots of large component trees.
Key points
- Query by role and label
- user-event for interactions
- Assert on visible behavior
Further reading: React Testing Library
Practice questions
Why prefer getByRole over getByTestId?
Answer
Role queries match how users and assistive technologies perceive the UI, so tests also verify accessibility; test IDs couple tests to implementation.
What's the difference between getBy, queryBy and findBy?
Answer
getBy throws if not found; queryBy returns null (for asserting absence); findBy returns a promise that waits for the element (for async UI).
Open in the app to mark lessons complete, take notes and track your progress.