React Testing in 2020: How to Test Functional Components with Jest and React Testing Library

Published on 5.12.2020

In 2020, testing React applications has undergone a shift toward more developer-friendly and reliable testing tools. While Enzyme has long been the go-to library for testing React components, many developers are now migrating towards React Testing Library for its more user-centric testing philosophy.

In this post, we will discuss:

  • Why React Testing Library is preferred over Enzyme in 2020.
  • How to write unit tests for React Hooks.
  • Best practices for mocking API calls in tests.

By the end of this guide, you’ll be ready to write more effective and maintainable tests for your React functional components using Jest and React Testing Library.


Moving Away from Enzyme: Why React Testing Library is the New Standard

In the past, Enzyme was a popular testing utility for React, allowing developers to mount components, interact with them, and assert certain behaviors. However, Enzyme is more geared towards testing the implementation details of components, which can lead to tests that are tightly coupled to your component’s internal workings.

On the other hand, React Testing Library encourages developers to test their components from a user-centric perspective. The idea is to test components as they will be used in the real world, focusing on the UI behavior and interactions rather than the internal implementation.

Why React Testing Library is better:

  • Focus on user interactions: You write tests that simulate how users interact with your app.
  • Minimal mocking: The library encourages you to write tests that don’t rely heavily on mocking or implementation details.
  • Promotes good testing practices: It helps you avoid tightly coupling your tests to the component’s internal implementation, which leads to more maintainable and future-proof tests.
  • Accessibility-first: React Testing Library emphasizes querying and interacting with elements in ways that reflect how users find them (e.g., using getByLabelTextgetByRole).

In short, React Testing Library provides a more holistic, user-focused approach to testing, which is why it’s becoming the standard in the React ecosystem.


Writing Unit Tests for React Hooks

One of the big challenges when testing React functional components is how to effectively test Hooks. With the introduction of hooks, the way you manage component state, side effects, and context has changed significantly. Fortunately, React Testing Library has made it easier to test hooks using utilities like renderHook.

1. Testing useState and useEffect with React Testing Library

Let’s start by writing a test for a simple custom hook that uses useState and useEffect. Here’s an example of a hook that fetches data from an API:

// useFetch.js
import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(url);
      const result = await response.json();
      setData(result);
      setLoading(false);
    };
    fetchData();
  }, [url]);

  return { data, loading };
}

export default useFetch;

Now, let’s write a test for this hook:

// useFetch.test.js
import { renderHook, act } from '@testing-library/react-hooks';
import useFetch from './useFetch';

test('should fetch data and update state', async () => {
  const { result, waitForNextUpdate } = renderHook(() => useFetch('https://api.example.com/data'));

  // Initial loading state
  expect(result.current.loading).toBe(true);

  // Wait for the data to load
  await waitForNextUpdate();

  // After data is loaded
  expect(result.current.data).toBeDefined();
  expect(result.current.loading).toBe(false);
});

In this test:

  • We use renderHook from @testing-library/react-hooks to render the hook.
  • The waitForNextUpdate function is used to wait for the hook to finish its asynchronous action.
  • We assert that the loading state is initially true and later becomes false after the data is fetched.

2. Testing Custom Hooks with State and Effects

If your custom hook is more complex and involves additional state, side effects, or context, you can extend this pattern to test various behaviors, such as handling error states or multiple side effects.


Best Practices for Mocking API Calls in Tests

When writing tests for components that make API calls, mocking these calls is essential to avoid actual network requests. Jest provides powerful tools for mocking modules and functions, which allows you to isolate your tests and ensure that they are deterministic.

1. Mocking Fetch API

For a component or hook that relies on the fetch API, you can mock it like this:

// Component that uses fetch
import React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch('/data');
      const result = await response.json();
      setData(result);
    };

    fetchData();
  }, []);

  if (!data) return <div>Loading...</div>;

  return <div>{data}</div>;
}

export default DataFetcher;

Now, in the test, we can mock the fetch call:

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

// Mock the fetch function
global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve({ message: 'Hello, world!' }),
  })
);

test('should fetch and display data', async () => {
  render(<DataFetcher />);

  // Wait for the component to re-render after fetch
  await waitFor(() => screen.getByText(/Hello, world!/));

  expect(screen.getByText(/Hello, world!/)).toBeInTheDocument();
});

In this test:

  • We mock fetch globally using Jest’s jest.fn().
  • We use waitFor to wait for the component to re-render after the fetch completes.
  • We assert that the data is rendered correctly once the API call resolves.

2. Mocking Axios Calls

If you’re using Axios for API calls, you can mock Axios in a similar way:

import axios from 'axios';
jest.mock('axios');

test('should fetch data using Axios', async () => {
  axios.get.mockResolvedValue({ data: { message: 'Hello from Axios' } });

  render(<DataFetcher />);

  await waitFor(() => screen.getByText(/Hello from Axios/));

  expect(screen.getByText(/Hello from Axios/)).toBeInTheDocument();
});

This ensures that API calls are properly mocked, and no real network requests are made during testing.


Conclusion

In 2020, testing React applications has evolved towards more user-centric testing methodologies, with React Testing Library taking center stage. It offers a simpler, more intuitive way to test components by focusing on how users interact with your app, rather than testing implementation details.

By leveraging React Testing Library and Jest, you can easily:

  • Test React Hooks and components.
  • Mock API calls and external dependencies.
  • Write maintainable and user-focused tests that ensure your application works as expected.

If you’re migrating from Enzyme or just getting started with testing React, these tools will provide a more modern, reliable testing experience that helps catch issues early and improves your overall development process.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

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