Skip to content
intermediatePhase 40 · Testing

Component Testing

Test React components with React Testing Library.

1h
0 problems
Topic Progress0%

React Testing Library

React Testing Library

Test components from the user's perspective.

Core Principles

  1. Test behavior, not implementation
  2. Use queries that users would use
  3. Avoid testing internal state

Setup

npm install -D @testing-library/react @testing-library/user-events

Basic Test

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

describe('Counter', () => {
  it('should increment count when clicked', async () => {
    const user = userEvent.setup();
    render(<Counter />);

    const button = screen.getByRole('button', { name: /increment/i });
    
    await user.click(button);
    
    expect(screen.getByText('Count: 1')).toBeInTheDocument();
  });
});

Query Priority

// Priority order for queries
1. getByRole - Accessible names
2. getByLabelText - Form labels
3. getByPlaceholderText - Placeholders
4. getByText - Visible text
5. getByDisplayValue - Form values
6. getByAltText - Image alt text
7. getByTitle - Title attribute
8. getByTestId - Last resort (data-testid)

Query Types

// Single element
getByText('Submit'); // Throws if not found
queryByText('Submit'); // Returns null if not found

// Multiple elements
getAllByText('Submit'); // Returns array, throws if empty
queryAllByText('Submit'); // Returns array, empty if none found

// Async
findByText('Submit'); // Waits for element to appear
findAllByText('Submit'); // Waits for multiple elements

Rendering Components

Rendering Components

Test different component states and props.

Props and State

describe('UserProfile', () => {
  it('should render user name', () => {
    render(<UserProfile name="John" />);
    expect(screen.getByText('John')).toBeInTheDocument();
  });

  it('should render loading state', () => {
    render(<UserProfile loading />);
    expect(screen.getByText('Loading...')).toBeInTheDocument();
  });

  it('should render error state', () => {
    render(<UserProfile error="Failed to load" />);
    expect(screen.getByRole('alert')).toHaveTextContent('Failed to load');
  });
});

Conditional Rendering

describe('Notification', () => {
  it('should show notification when hasNotification is true', () => {
    render(<App hasNotification />);
    expect(screen.getByText('New notification')).toBeInTheDocument();
  });

  it('should hide notification when hasNotification is false', () => {
    render(<App />);
    expect(screen.queryByText('New notification')).not.toBeInTheDocument();
  });
});

List Rendering

describe('TodoList', () => {
  const todos = [
    { id: 1, text: 'Learn React' },
    { id: 2, text: 'Write tests' },
  ];

  it('should render all todos', () => {
    render(<TodoList todos={todos} />);
    
    expect(screen.getByText('Learn React')).toBeInTheDocument();
    expect(screen.getByText('Write tests')).toBeInTheDocument();
  });

  it('should render correct number of items', () => {
    render(<TodoList todos={todos} />);
    
    const items = screen.getAllByRole('listitem');
    expect(items).toHaveLength(2);
  });
});

Component with Children

describe('Card', () => {
  it('should render children', () => {
    render(
      <Card>
        <h2>Card Title</h2>
        <p>Card content</p>
      </Card>
    );

    expect(screen.getByText('Card Title')).toBeInTheDocument();
    expect(screen.getByText('Card content')).toBeInTheDocument();
  });
});

User Events

User Events

Test user interactions realistically.

Click Events

describe('Button', () => {
  it('should call onClick when clicked', async () => {
    const user = userEvent.setup();
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click me</Button>);

    await user.click(screen.getByText('Click me'));

    expect(handleClick).toHaveBeenCalledTimes(1);
  });
});

Type Events

describe('Input', () => {
  it('should update value when typing', async () => {
    const user = userEvent.setup();
    render(<Input />);

    const input = screen.getByRole('textbox');
    await user.type(input, 'Hello World');

    expect(input).toHaveValue('Hello World');
  });

  it('should call onChange when typing', async () => {
    const user = userEvent.setup();
    const handleChange = jest.fn();
    render(<Input onChange={handleChange} />);

    await user.type(screen.getByRole('textbox'), 'a');

    expect(handleChange).toHaveBeenCalledWith('a');
  });
});

Keyboard Events

describe('SearchInput', () => {
  it('should search on Enter key', async () => {
    const user = userEvent.setup();
    const handleSearch = jest.fn();
    render(<SearchInput onSearch={handleSearch} />);

    await user.type(screen.getByRole('textbox'), 'react');
    await user.keyboard('{Enter}');

    expect(handleSearch).toHaveBeenCalledWith('react');
  });
});

Complex Interactions

describe('Dropdown', () => {
  it('should select option', async () => {
    const user = userEvent.setup();
    render(
      <Dropdown options={['Option 1', 'Option 2', 'Option 3']} />
    );

    // Open dropdown
    await user.click(screen.getByText('Select...'));
    expect(screen.getByRole('listbox')).toBeInTheDocument();

    // Select option
    await user.click(screen.getByText('Option 2'));
    expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
    expect(screen.getByText('Option 2')).toBeInTheDocument();
  });
});

Form Submission

describe('ContactForm', () => {
  it('should submit form data', async () => {
    const user = userEvent.setup();
    const onSubmit = jest.fn();
    render(<ContactForm onSubmit={onSubmit} />);

    await user.type(screen.getByLabelText('Name'), 'John Doe');
    await user.type(screen.getByLabelText('Email'), 'john@example.com');
    await user.type(screen.getByLabelText('Message'), 'Hello!');
    await user.click(screen.getByRole('button', { name: /send/i }));

    await waitFor(() => {
      expect(onSubmit).toHaveBeenCalledWith({
        name: 'John Doe',
        email: 'john@example.com',
        message: 'Hello!',
      });
    });
  });
});

Practice Problems

0/3solved
Build Component Testing Component

Create a reusable React component implementing Component Testing. Include proper state management and accessibility.

Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where needed
Component Testing Testing

Write unit and integration tests for Component Testing using React Testing Library.

Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility tests
Component Testing Performance

Optimize Component Testing for performance. Consider memoization, code splitting, and bundle size.

Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysis

Quiz

1. What is the priority order for queries in React Testing Library?

Question 1 options

2. Why use userEvent instead of fireEvent?

Question 2 options

3. What is the primary purpose of Component Testing?

Question 3 options

4. What is a common mistake when implementing Component Testing?

Question 4 options

Flashcards

Question

What is React Testing Library?

Answer

A library for testing React components from the user's perspective.

Question

Why test from user's perspective?

Answer

Tests are more robust and don't break when implementation changes.

Question

What is the findBy query?

Answer

An async query that waits for an element to appear in the DOM.

Question

When should you use queryByText?

Answer

When testing that an element is NOT present (returns null instead of throwing).

Question

What is Component Testing?

Answer

Component Testing is a key concept in frontend development.

Revision Notes

Key Takeaways

  • 1.React Testing Library tests from user's perspective
  • 2.Use getByRole as highest priority query
  • 3.userEvent simulates real user behavior
  • 4.Test behavior, not implementation details
  • 5.Use queryByText for asserting elements don't exist

Interview Tips

  • Explain the query priority order
  • Discuss why testing from user perspective is better
  • Know the difference between get and query

Cheat Sheet

Component Testing Cheat Sheet

Query Priority

  1. getByRole
  2. getByLabelText
  3. getByText
  4. getByTestId (last resort)

Query Types

  • get: Throws if not found
  • query: Returns null if not found
  • find: Async, waits for element

userEvent

const user = userEvent.setup();
await user.click(element);
await user.type(input, 'text');
await user.keyboard('{Enter}');

Best Tests

  • Test behavior, not implementation
  • Use accessible queries
  • Test from user's perspective