Isaac.

Testing React Components

Write effective unit and integration tests for React applications.

By EMEPublished: February 20, 2025
reacttestingjesttesting libraryunit tests

A Simple Analogy

Testing React components is like quality assurance for assembly parts. Each component is tested independently and in combination.


Why Component Tests?

  • Regression prevention: Catch breaking changes
  • Refactoring confidence: Safe code changes
  • Documentation: Tests show expected behavior
  • Maintainability: Early issue detection
  • User experience: Test actual interactions

Jest Setup

// package.json
{
  "devDependencies": {
    "@testing-library/react": "^14.0.0",
    "@testing-library/jest-dom": "^6.1.0",
    "jest": "^29.0.0"
  }
}

// jest.config.js
export default {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/test/setup.ts'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  }
}

Component Testing

import { render, screen } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('renders with text', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });
  
  it('calls onClick when clicked', () => {
    const onClick = jest.fn();
    render(<Button onClick={onClick}>Click</Button>);
    
    screen.getByText('Click').click();
    expect(onClick).toHaveBeenCalledTimes(1);
  });
  
  it('disables when prop is true', () => {
    render(<Button disabled>Disabled</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
  });
});

User Events

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';

describe('LoginForm', () => {
  it('submits form with valid input', async () => {
    const user = userEvent.setup();
    const onSubmit = jest.fn();
    
    render(<LoginForm onSubmit={onSubmit} />);
    
    await user.type(screen.getByLabelText('Email'), 'test@example.com');
    await user.type(screen.getByLabelText('Password'), 'password123');
    await user.click(screen.getByRole('button', { name: /submit/i }));
    
    expect(onSubmit).toHaveBeenCalledWith({
      email: 'test@example.com',
      password: 'password123'
    });
  });
});

Async Testing

import { render, screen, waitFor } from '@testing-library/react';
import { UserList } from './UserList';

describe('UserList', () => {
  it('loads and displays users', async () => {
    render(<UserList />);
    
    // Component is loading
    expect(screen.getByText(/loading/i)).toBeInTheDocument();
    
    // Wait for data to load
    await waitFor(() => {
      expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
    });
    
    // Users displayed
    expect(screen.getByText('Alice')).toBeInTheDocument();
    expect(screen.getByText('Bob')).toBeInTheDocument();
  });
});

Mocking Modules

import { render, screen } from '@testing-library/react';
import { ProductPage } from './ProductPage';
import * as api from '@/api/products';

jest.mock('@/api/products');

describe('ProductPage', () => {
  beforeEach(() => {
    jest.mocked(api.getProduct).mockResolvedValue({
      id: '1',
      name: 'Widget',
      price: 29.99
    });
  });
  
  it('displays product data', async () => {
    render(<ProductPage productId="1" />);
    
    expect(await screen.findByText('Widget')).toBeInTheDocument();
    expect(screen.getByText('$29.99')).toBeInTheDocument();
  });
});

Snapshot Testing

import { render } from '@testing-library/react';
import { Card } from './Card';

describe('Card', () => {
  it('renders correctly', () => {
    const { container } = render(
      <Card title="Test">
        <p>Content</p>
      </Card>
    );
    
    expect(container).toMatchSnapshot();
  });
});

Best Practices

  1. Test behavior: Not implementation
  2. Semantic queries: Use accessible queries
  3. User events: Simulate real interactions
  4. Avoid snapshots: Prefer specific assertions
  5. Test user flows: Integration tests matter

Related Concepts

  • End-to-end testing
  • Visual regression
  • Performance testing
  • Accessibility testing

Summary

Use React Testing Library to test components through user interactions rather than implementation details. Focus on what users see and do.