Writing Tests with React Testing Library

Loading

React Testing Library (RTL) is a testing utility that encourages testing your components the way users would interact with them. It provides simple utilities for rendering components, querying elements, and simulating events, with a strong emphasis on accessibility and user-centric testing.

This guide will walk through how to write tests for your React components using React Testing Library, covering common patterns and practices.

1. Installing React Testing Library

If you are using Create React App (CRA), React Testing Library comes pre-installed. Otherwise, you can manually install it by running the following command:

npm install --save-dev @testing-library/react @testing-library/jest-dom
  • @testing-library/react: Core package for rendering components and interacting with the DOM.
  • @testing-library/jest-dom: Extends Jest’s expect with useful matchers like toBeInTheDocument, toHaveTextContent, etc.

2. Basic Testing Workflow

The basic workflow for testing a React component involves:

  1. Render the Component: Use render() to render the component into a test DOM.
  2. Query the Elements: Use queries to locate DOM elements based on various criteria (e.g., getByText, getByRole).
  3. Interact with Elements: Simulate user interactions like clicks, typing, etc., using fireEvent.
  4. Assert the Results: Use Jest assertions to check the expected behavior.

3. Example Component to Test

Let’s create a simple component that we can use to demonstrate testing.

Example: Counter.js

import React, { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

export default Counter;

This is a simple counter component with two buttons to increment and decrement the count.

4. Basic Test for the Counter Component

Now, let’s write tests for the Counter component. We’ll check if:

  1. The initial count is displayed correctly.
  2. Clicking the “Increment” button increases the count.
  3. Clicking the “Decrement” button decreases the count.

Test: Counter.test.js

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

describe('Counter Component', () => {
  test('displays initial count', () => {
    render(<Counter />);

    // Get the element displaying the count
    const countElement = screen.getByText(/Counter:/i);
    
    // Assert that the initial count is 0
    expect(countElement).toHaveTextContent('Counter: 0');
  });

  test('increments the count when Increment button is clicked', () => {
    render(<Counter />);
    
    const incrementButton = screen.getByText(/Increment/i);
    const countElement = screen.getByText(/Counter:/i);
    
    // Click the increment button
    fireEvent.click(incrementButton);
    
    // Assert that the count increased
    expect(countElement).toHaveTextContent('Counter: 1');
  });

  test('decrements the count when Decrement button is clicked', () => {
    render(<Counter />);
    
    const decrementButton = screen.getByText(/Decrement/i);
    const countElement = screen.getByText(/Counter:/i);
    
    // Click the decrement button
    fireEvent.click(decrementButton);
    
    // Assert that the count decreased
    expect(countElement).toHaveTextContent('Counter: -1');
  });
});

5. Explanation of the Test Code

  • render(<Counter />): This renders the Counter component into the test DOM.
  • screen.getByText(/Counter:/i): This query finds the element displaying the text Counter:, using a case-insensitive match (i flag).
  • fireEvent.click(incrementButton): This simulates a click event on the “Increment” button.
  • expect(countElement).toHaveTextContent('Counter: 1'): This assertion checks that the text content of the count element has changed to Counter: 1.

6. Different Querying Methods

React Testing Library provides several methods for querying elements. Here are some of the most common:

  1. getByText: Finds elements by their text content.
  2. getByRole: Finds elements by their role, like buttons, headings, etc.
  3. getByLabelText: Finds form elements like input fields by their associated label text.
  4. getByTestId: Finds elements by a data-testid attribute (less preferred; use it sparingly).
  5. getByPlaceholderText: Finds input elements by their placeholder text.

Example of getByRole:

test('displays two buttons', () => {
  render(<Counter />);
  
  // Query buttons by role
  const buttons = screen.getAllByRole('button');
  
  // Assert there are two buttons
  expect(buttons).toHaveLength(2);
});

7. Simulating User Interactions

React Testing Library allows you to simulate user interactions like clicking, typing, or submitting forms. The most common method is using fireEvent.

Simulating a Click:

fireEvent.click(button);

Simulating Typing:

fireEvent.change(inputElement, { target: { value: 'New Value' } });

8. Async Testing (Handling Delays or API Calls)

In real-world apps, you may need to handle asynchronous behavior, such as waiting for data from an API or waiting for an element to appear after a delay. React Testing Library provides several utilities for this, like findBy queries and waitFor.

Example: Testing Async Behavior

Suppose we have a component that fetches data asynchronously.

Example: DataFetcher.js

import React, { useState, useEffect } from 'react';

const DataFetcher = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    setTimeout(() => {
      setData('Fetched Data');
    }, 2000);
  }, []);

  return <div>{data ? data : 'Loading...'}</div>;
};

export default DataFetcher;

Test: DataFetcher.test.js

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

test('displays loading text initially, then fetched data', async () => {
  render(<DataFetcher />);
  
  // Initially, the loading text should be displayed
  expect(screen.getByText(/Loading.../i)).toBeInTheDocument();
  
  // Wait for the data to be fetched
  const dataElement = await screen.findByText(/Fetched Data/i);
  
  // After 2 seconds, the fetched data should be displayed
  expect(dataElement).toBeInTheDocument();
});

Explanation:

  • findByText: This is an asynchronous query that waits for the element with the specified text to appear.
  • await: We use await to wait for the element to appear before making assertions.

9. Cleaning Up Between Tests

React Testing Library automatically cleans up the rendered components between tests. This ensures that one test doesn’t affect the next.

Leave a Reply

Your email address will not be published. Required fields are marked *