The React testing landscape has shifted dramatically. In 2018, Enzyme was the standard, allowing developers to manipulate component internals (state, props). In 2020, React Testing Library (RTL) is the undisputed champion. The philosophy “The more your tests resemble the way your software is used, the more confidence they can give you” drives this shift.
Enzyme (Implementation Details) vs RTL (User Behavior)
| Action | Enzyme (Do NOT use) | React Testing Library (Do use) |
|---|---|---|
| Selection | wrapper.find('button') | screen.getByRole('button', {name: /submit/i}) |
| State | wrapper.setState({ open: true }) | fireEvent.click(button) |
| Assertion | expect(wrapper.state('open')).toBe(true) | expect(screen.getByText('Content')).toBeVisible() |
Writing a Robust Test
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Login from './Login';
test('shows error when login fails', async () => {
render(<Login />);
// Interact like a user
userEvent.type(screen.getByLabelText(/username/i), 'baduser');
userEvent.type(screen.getByLabelText(/password/i), 'wrongpass');
userEvent.click(screen.getByRole('button', { name: /sign in/i }));
// Wait for async effect
await waitFor(() => {
expect(screen.getByRole('alert')).toHaveTextContent(/invalid credentials/i);
});
});
Testing Hooks
To test custom hooks in isolation, use renderHook from @testing-library/react-hooks.
import { renderHook, act } from '@testing-library/react-hooks';
import useCounter from './useCounter';
test('should increment counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
Key Takeaways
- Stop testing implementation details (state, class methods).
- Query by accessibility roles (
getByRole) to ensure inclusive apps. - Use
user-eventfor more realistic interaction simulation.
Discover more from C4: Container, Code, Cloud & Context
Subscribe to get the latest posts sent to your email.