Introduction
Test-Driven Development in frontend development, particularly with React, requires a different approach than backend testing. This article explores how to effectively test React components and frontend logic.
Component Testing
// Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
describe('Button', () => {
test('should render with text', () => {
render(Click me);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
test('should call onClick when clicked', () => {
const handleClick = jest.fn();
render(Click me);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalled();
});
test('should be disabled when disabled prop is true', () => {
render(Click me);
expect(screen.getByText('Click me')).toBeDisabled();
});
});
Form Testing
// LoginForm.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import LoginForm from './LoginForm';
describe('LoginForm', () => {
test('should submit form with valid data', async () => {
const handleSubmit = jest.fn();
render();
fireEvent.change(screen.getByLabelText('Email'), {
target: { value: 'john@example.com' }
});
fireEvent.change(screen.getByLabelText('Password'), {
target: { value: 'password123' }
});
fireEvent.click(screen.getByText('Login'));
expect(handleSubmit).toHaveBeenCalledWith({
email: 'john@example.com',
password: 'password123'
});
});
test('should show validation errors', async () => {
render();
fireEvent.click(screen.getByText('Login'));
expect(screen.getByText('Email is required')).toBeInTheDocument();
expect(screen.getByText('Password is required')).toBeInTheDocument();
});
});
Custom Hook Testing
// useCounter.test.js
import { renderHook, act } from '@testing-library/react-hooks';
import useCounter from './useCounter';
describe('useCounter', () => {
test('should increment counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
test('should decrement counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(-1);
});
test('should reset counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
result.current.reset();
});
expect(result.current.count).toBe(0);
});
});
Integration Testing
// UserProfile.test.jsx
import { render, screen, waitFor } from '@testing-library/react';
import UserProfile from './UserProfile';
import { UserProvider } from './UserContext';
describe('UserProfile', () => {
test('should load and display user data', async () => {
const mockUser = {
name: 'John Doe',
email: 'john@example.com'
};
render(
);
await waitFor(() => {
expect(screen.getByText(mockUser.name)).toBeInTheDocument();
expect(screen.getByText(mockUser.email)).toBeInTheDocument();
});
});
test('should handle loading state', () => {
render(
);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
});
Best Practices
- Test component behavior, not implementation
- Use React Testing Library
- Test user interactions
- Mock external dependencies
- Test accessibility
Conclusion
TDD in frontend development requires understanding both React's component model and modern testing tools. By following these practices, you can create robust and maintainable frontend applications.
"Frontend testing is about verifying that components work as users expect them to." - Kent C. Dodds
Member discussion: