React Testing Library (RTL) tests components the way users interact with them — by querying the DOM, not component internals. Today you will render components, fire events, and assert on screen output.
npm install --save-dev @testing-library/react @testing-library/user-event @testing-library/jest-dom// Button.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Button } from './Button';
test('renders label and calls onClick', async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Save</Button>);
const btn = screen.getByRole('button', { name: /save/i });
expect(btn).toBeInTheDocument();
await userEvent.click(btn);
expect(handleClick).toHaveBeenCalledTimes(1);
});// Form.test.jsx — async state updates
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';
test('shows error on empty submit', async () => {
render(<LoginForm />);
await userEvent.click(screen.getByRole('button', { name: /login/i }));
await waitFor(() => {
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
});
});
test('calls onSubmit with credentials', async () => {
const onSubmit = jest.fn();
render(<LoginForm onSubmit={onSubmit} />);
await userEvent.type(screen.getByLabelText(/email/i), '[email protected]');
await userEvent.type(screen.getByLabelText(/password/i), 'secret123');
await userEvent.click(screen.getByRole('button', { name: /login/i }));
expect(onSubmit).toHaveBeenCalledWith({
email: '[email protected]',
password: 'secret123',
});
});