In this module, you'll learn how to test React components effectively. You'll understand how to test components as their props change, use mocks in web application tests, and test asynchronous API calls within components.
When testing React components, it's essential to test how components behave as props change. The React Testing Library provides the rerender()
function that allows us to test components after props have been updated.
To test components as props change, we need to:
rerender()
function to update propsimport { render, screen } from '@testing-library/react'; import App from './App'; test('renders friends from the API', () => { // Pass an empty list on first render (data hasn't arrived from API) const { rerender } = render(<App friends={[]} />); // Assert that there are no friends yet, because they are still arriving expect(screen.queryAllByTestId('friend')).toHaveLength(0); // Assert that a "fetching" message is showing expect(screen.queryByText('Fetching friends...')).toBeInTheDocument(); // Rerender component with new props rerender( <App friends={[ { id: 1, name: 'Alla' }, { id: 2, name: 'Josh' } ]} /> ); // Assert that we have friends now, and that the "fetching" message is gone expect(screen.queryAllByTestId('friend')).toHaveLength(2); expect(screen.queryByText('Fetching friends...')).not.toBeInTheDocument(); });
Sometimes you need to assert that content is not present in the DOM. Use queryByRole
or other queryBy
assertions instead of getBy
assertions, as these return null
instead of throwing an error when an element isn't found.
// Assert content is not rendered expect(screen.queryByRole("alert")).toBeNull(); // Or use .not with toBeInTheDocument() expect(screen.queryByText('Error message')).not.toBeInTheDocument();
Mocks are essential for isolating the behavior of functions in your tests, especially when dealing with dependencies that would make testing difficult, such as random ID generators or API calls.
Mocks provide two key benefits:
Consider a component that uses the nanoid
library to generate unique IDs:
import { nanoid } from "nanoid"; export const makeUser = (firstName, lastName) => { return { id: nanoid(), fullName: `${firstName} ${lastName}` }; };
Testing this would be challenging because nanoid()
generates a random ID each time. We can mock the nanoid function to return a predictable value:
// At the top of your test file jest.mock('nanoid', () => ({ nanoid: () => 'abcde', })); test("generates a user with an id and a full name", () => { // Arrange const expected = { id: "abcde", fullName: "Peter Parker" }; // Act const actual = makeUser("Peter", "Parker"); // Assert expect(actual).toEqual(expected); });
The mock replaces the real nanoid
module with our controlled version, making the tests predictable and reliable.
Testing asynchronous operations such as API calls requires special handling to ensure your tests wait for those operations to complete before making assertions.
React Testing Library provides the waitFor
function to help test asynchronous operations. Combined with mocking, it allows you to test components that make API calls without actually waiting for real network requests.
waitFor
waitFor
to wait for DOM updates after the async operationimport React from "react"; import { render, waitFor } from "@testing-library/react"; import userEvent from '@testing-library/user-event'; import { fetchDoggos as mockFetchDoggos } from "../api/fetchDoggos"; import Doggos from "./Doggos"; // Create mock before setting up test jest.mock("../api/fetchDoggos"); test("renders dog images from API", async () => { // Mock resolved results mockFetchDoggos.mockResolvedValueOnce({ message: [ "https://images.dog.ceo/breeds/hound-afghan/n02088094_1003.jpg", "https://images.dog.ceo/breeds/hound-afghan/n02088094_1007.jpg", "https://images.dog.ceo/breeds/hound-afghan/n02088094_1023.jpg" ] }); const { getByText, getAllByTestId } = render(); const user = userEvent.setup(); const fetchDoggosButton = getByText(/fetch doggos/i); await user.click(fetchDoggosButton); // Wait for the component to update after the async call await waitFor(() => { expect(getAllByTestId(/doggo-images/i)).toHaveLength(3); }); // Verify the mock was called expect(mockFetchDoggos).toHaveBeenCalledTimes(1); });
This approach allows you to test the full user flow, including the async API call, without actually making network requests.