Tutorials Logic, IN +91 8092939553 info@tutorialslogic.com
FAQs Support
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Interview Questions Website Development
Compiler Tutorials

Testing React Applications

Testing Stack

  • Vitest or Jest — test runner
  • React Testing Library (RTL) — test components from user's perspective
  • @testing-library/user-event — realistic user interactions
  • MSW (Mock Service Worker) — mock API calls
  • Playwright / Cypress — end-to-end testing

React Testing Library's philosophy: "The more your tests resemble the way your software is used, the more confidence they can give you." Test behavior, not implementation details.

React Testing Library — Component Tests
# Install testing dependencies
npm install -D vitest @testing-library/react @testing-library/user-event
npm install -D @testing-library/jest-dom jsdom

# vite.config.js
# test: {
#   globals: true,
#   environment: 'jsdom',
#   setupFiles: './src/test/setup.js',
# }

# src/test/setup.js
# import '@testing-library/jest-dom'

# package.json
# "test": "vitest",
# "test:run": "vitest run",
# "test:ui": "vitest --ui"

# Run tests
npm test          # watch mode
npm run test:run  # single run
// Counter.test.jsx
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Counter from '../Counter'

describe('Counter', () => {
    it('renders initial count of 0', () => {
        render(<Counter />)
        // getByText — throws if not found
        expect(screen.getByText('Count: 0')).toBeInTheDocument()
    })

    it('increments count when + button is clicked', async () => {
        const user = userEvent.setup()
        render(<Counter />)

        // userEvent simulates real user interactions
        await user.click(screen.getByRole('button', { name: '+' }))
        expect(screen.getByText('Count: 1')).toBeInTheDocument()
    })

    it('decrements count when - button is clicked', async () => {
        const user = userEvent.setup()
        render(<Counter initialCount={5} />)

        await user.click(screen.getByRole('button', { name: '-' }))
        expect(screen.getByText('Count: 4')).toBeInTheDocument()
    })

    it('resets to 0 when reset button is clicked', async () => {
        const user = userEvent.setup()
        render(<Counter initialCount={10} />)

        await user.click(screen.getByRole('button', { name: /reset/i }))
        expect(screen.getByText('Count: 0')).toBeInTheDocument()
    })

    it('calls onCountChange when count changes', async () => {
        const user = userEvent.setup()
        const onCountChange = vi.fn()
        render(<Counter onCountChange={onCountChange} />)

        await user.click(screen.getByRole('button', { name: '+' }))
        expect(onCountChange).toHaveBeenCalledWith(1)
        expect(onCountChange).toHaveBeenCalledTimes(1)
    })

    it('does not go below 0', async () => {
        const user = userEvent.setup()
        render(<Counter />)

        await user.click(screen.getByRole('button', { name: '-' }))
        expect(screen.getByText('Count: 0')).toBeInTheDocument()
    })
})
// UserList.test.jsx — testing async component
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import UserList from '../UserList'

const mockUsers = [
    { id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin' },
    { id: 2, name: 'Bob',   email: 'bob@example.com',   role: 'user' },
]

describe('UserList', () => {
    beforeEach(() => {
        vi.restoreAllMocks()
    })

    it('shows loading state initially', () => {
        global.fetch = vi.fn(() => new Promise(() => {}))
        render(<UserList />)
        expect(screen.getByText(/loading/i)).toBeInTheDocument()
    })

    it('renders users after successful fetch', async () => {
        global.fetch = vi.fn(() =>
            Promise.resolve({ ok: true, json: () => Promise.resolve(mockUsers) })
        )

        render(<UserList />)

        // Wait for async content
        await waitFor(() => {
            expect(screen.getByText('Alice')).toBeInTheDocument()
        })

        expect(screen.getByText('Bob')).toBeInTheDocument()
        expect(screen.queryByText(/loading/i)).not.toBeInTheDocument()
    })

    it('shows error message on fetch failure', async () => {
        global.fetch = vi.fn(() => Promise.reject(new Error('Network error')))

        render(<UserList />)

        await waitFor(() => {
            expect(screen.getByText(/network error/i)).toBeInTheDocument()
        })
    })

    it('filters users by search input', async () => {
        const user = userEvent.setup()
        global.fetch = vi.fn(() =>
            Promise.resolve({ ok: true, json: () => Promise.resolve(mockUsers) })
        )

        render(<UserList />)
        await waitFor(() => screen.getByText('Alice'))

        // Type in search box
        await user.type(screen.getByPlaceholderText(/search/i), 'Alice')

        expect(screen.getByText('Alice')).toBeInTheDocument()
        expect(screen.queryByText('Bob')).not.toBeInTheDocument()
    })

    it('deletes user when delete button is clicked', async () => {
        const user = userEvent.setup()
        global.fetch = vi.fn(() =>
            Promise.resolve({ ok: true, json: () => Promise.resolve(mockUsers) })
        )

        render(<UserList />)
        await waitFor(() => screen.getByText('Alice'))

        // Find delete button for Alice
        const deleteButtons = screen.getAllByRole('button', { name: /delete/i })
        await user.click(deleteButtons[0])

        expect(screen.queryByText('Alice')).not.toBeInTheDocument()
        expect(screen.getByText('Bob')).toBeInTheDocument()
    })
})

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.