Frontend (Component) Testing
Frontend testing ensures that your UI components behave correctly from the user’s perspective. We use React Testing Library (RTL), which encourages testing how the user interacts with your app (clicking buttons, reading text) rather than implementation details (state, props).
Core Concepts
Section titled “Core Concepts”RTL provides a few key utilities:
render: Renders a React component into a virtual DOM for testing.screen: A global object to query the rendered DOM (e.g.,screen.getByText).fireEvent/userEvent: Simulates user actions like clicking or typing.
Queries
Section titled “Queries”The most common way to find elements is by their accessible role or text:
getByRole('button', { name: /submit/i })getByLabelText(/email/i)getByText(/welcome/i)
Setup with Bun
Section titled “Setup with Bun”To use RTL with Bun, you typically need happy-dom or jsdom to simulate the browser environment.
bun add -d @testing-library/react @testing-library/dom happy-domYou also need to configure your bunfig.toml or test setup file to load the DOM environment.
Example: Testing a Login Form
Section titled “Example: Testing a Login Form”Let’s say you have a LoginForm component. We want to verify that:
- It renders the email and password inputs.
- It shows an error if submitted empty.
- It calls the submit handler when filled correctly.
import { describe, test, expect, mock } from "bun:test";import { render, screen, fireEvent } from "@testing-library/react";import { LoginForm } from "./login-form";
// Mock the router and query hooks if used inside the componentmock.module("@tanstack/react-router", () => ({ useNavigate: () => mock(() => {}), Link: ({ children }) => <div>{children}</div>,}));
describe("LoginForm", () => { test("renders email and password inputs", () => { render(<LoginForm />);
expect(screen.getByLabelText(/email/i)).toBeTruthy(); expect(screen.getByLabelText(/password/i)).toBeTruthy(); expect(screen.getByRole("button", { name: /sign in/i })).toBeTruthy(); });
test("updates input values", () => { render(<LoginForm />);
const emailInput = screen.getByLabelText(/email/i); fireEvent.change(emailInput, { target: { value: "test@example.com" } });
expect(emailInput.value).toBe("test@example.com"); });});Best Practices
Section titled “Best Practices”- Test Behavior, Not Implementation: Don’t check if a state variable changed. Check if the UI updated (e.g., a success message appeared).
- Use Accessible Queries: Prefer
getByRoleandgetByLabelTextovergetByTestIdor CSS selectors. This ensures your app is accessible. - Mock External Calls: If your component calls an API (like
signIn), mock that function to avoid real network requests during tests.