Testing React Applications: Complete Guide for 2024

Master React testing with Jest, React Testing Library, Vitest, and best practices for unit, integration, and end-to-end testing.

3 min read
By Sagar Tank
TestingReactJestReact Testing LibraryVitestTDD
T
Image
Testing React Applications: Complete Guide for 2024

Testing is essential for building reliable React applications. A comprehensive testing strategy ensures your code works correctly and prevents regressions.

Testing Pyramid

  1. Unit Tests (70%) - Test individual functions and components
  2. Integration Tests (20%) - Test component interactions
  3. E2E Tests (10%) - Test complete user flows

Setting Up Testing

Jest + React Testing Library

pnpm add -D @testing-library/react @testing-library/jest-dom jest jest-environment-jsdom

Vitest (Modern Alternative)

pnpm add -D vitest @testing-library/react @testing-library/jest-dom

Component Testing

Basic Component Test

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

test('renders button with text', () => {
  render(<Button>Click me</Button>)
  expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument()
})

Testing User Interactions

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

test('button click increments counter', async () => {
  const user = userEvent.setup()
  render(<Counter />)
  
  const button = screen.getByRole('button', { name: /increment/i })
  await user.click(button)
  
  expect(screen.getByText(/count: 1/i)).toBeInTheDocument()
})

Testing Async Operations

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

test('loads and displays user data', async () => {
  render(<UserProfile userId="123" />)
  
  expect(screen.getByText(/loading/i)).toBeInTheDocument()
  
  await waitFor(() => {
    expect(screen.getByText(/john doe/i)).toBeInTheDocument()
  })
})

Mocking

Mock Functions

import { vi } from 'vitest'

test('calls onSubmit with form data', async () => {
  const handleSubmit = vi.fn()
  render(<Form onSubmit={handleSubmit} />)
  
  await userEvent.type(screen.getByLabelText(/email/i), 'test@example.com')
  await userEvent.click(screen.getByRole('button', { name: /submit/i }))
  
  expect(handleSubmit).toHaveBeenCalledWith({
    email: 'test@example.com'
  })
})

Mock API Calls

import { vi } from 'vitest'

vi.mock('./api', () => ({
  fetchUser: vi.fn(() => Promise.resolve({ name: 'John Doe' }))
}))

Testing Custom Hooks

import { renderHook, act } from '@testing-library/react'
import { useCounter } from './useCounter'

test('increments counter', () => {
  const { result } = renderHook(() => useCounter())
  
  act(() => {
    result.current.increment()
  })
  
  expect(result.current.count).toBe(1)
})

Testing Forms

test('validates and submits form', async () => {
  const user = userEvent.setup()
  render(<ContactForm />)
  
  const emailInput = screen.getByLabelText(/email/i)
  const submitButton = screen.getByRole('button', { name: /submit/i })
  
  // Test validation
  await user.click(submitButton)
  expect(screen.getByText(/email is required/i)).toBeInTheDocument()
  
  // Test submission
  await user.type(emailInput, 'test@example.com')
  await user.click(submitButton)
  
  await waitFor(() => {
    expect(screen.getByText(/thank you/i)).toBeInTheDocument()
  })
})

Best Practices

  1. Test behavior, not implementation - Focus on what users see and do
  2. Use accessible queries - Prefer getByRole, getByLabelText
  3. Keep tests simple - One assertion per test when possible
  4. Test user flows - Test how users interact with your app
  5. Mock external dependencies - Isolate your components

Coverage Goals

  • Statements: 80%+
  • Branches: 75%+
  • Functions: 80%+
  • Lines: 80%+

Conclusion

A solid testing strategy gives you confidence in your code. Start with component tests, add integration tests for critical flows, and use E2E tests sparingly for key user journeys.

Enjoyed this article?

Check out more articles on the blog or get in touch to discuss your project.