How to Test React Lists in React

Testing dynamically rendered lists is a crucial part of building robust React applications. This article outlines the step-by-step process of testing React lists using React Testing Library (RTL) and Jest, focusing on verifying list item counts, asserting content accuracy, and handling empty states.

The Component under Test

To demonstrate how to test lists, we will use a simple UserList component. This component accepts an array of users and renders them as an unordered list. If the list is empty, it displays a fallback message.

import React from 'react';

export function UserList({ users = [] }) {
  if (users.length === 0) {
    return <p>No users found.</p>;
  }

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

1. Testing List Rendering

When testing a list, your primary goal is to ensure that the correct number of items render and that the content of those items is correct.

The best practice in React Testing Library is to query elements by their ARIA role. For lists, we use the listitem role.

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

test('renders a list of users', () => {
  const mockUsers = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ];

  render(<UserList users={mockUsers} />);

  // Retrieve all list items
  const listItems = screen.getAllByRole('listitem');

  // Assert the correct number of items are rendered
  expect(listItems).toHaveLength(2);

  // Assert the correct content is displayed
  expect(listItems[0]).toHaveTextContent('Alice');
  expect(listItems[1]).toHaveTextContent('Bob');
});

2. Testing Empty States

It is equally important to test the behavior of the component when the data array is empty. This ensures your fallback UI works as expected.

test('renders fallback text when user list is empty', () => {
  render(<UserList users={[]} />);

  // Check that the fallback text is in the document
  const fallbackMessage = screen.getByText('No users found.');
  expect(fallbackMessage).toBeInTheDocument();

  // Ensure no list items are rendered
  const listItems = screen.queryAllByRole('listitem');
  expect(listItems).toHaveLength(0);
});

3. Testing Dynamic Lists (Adding/Removing Items)

If your list allows users to add or remove items dynamically, you should test the user interaction. In this scenario, we use @testing-library/user-event to simulate adding an item to the list.

Assuming a parent component manages the list state and renders an “Add User” button:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { App } from './App'; // Component that manages list state

test('allows users to add items to the list', async () => {
  render(<App />);

  // Assert initial state (e.g., 2 items)
  let listItems = screen.getAllByRole('listitem');
  expect(listItems).toHaveLength(2);

  // Simulate entering a new user name
  const input = screen.getByRole('textbox', { name: /user name/i });
  await userEvent.type(input, 'Charlie');

  // Simulate clicking the "Add" button
  const button = screen.getByRole('button', { name: /add user/i });
  await userEvent.click(button);

  // Assert list has updated to 3 items
  listItems = screen.getAllByRole('listitem');
  expect(listItems).toHaveLength(3);
  expect(listItems[2]).toHaveTextContent('Charlie');
});