DEV Community

Cover image for How To Write Unit Tests in React? A Detailed Guide
Ayushi Shrivastava for Quokka Labs

Posted on

How To Write Unit Tests in React? A Detailed Guide

React unit testing is a testing method where an individual or small unit of codes or components of the software are tested. In the context of React, a "unit" can be a single component or a small piece of logic within an element. React unit testing aims to validate that each unit of the app is working as intended.

This guide provides a quick introduction to writing React unit tests. I will cover the basics of setting up the testing environment, writing simple tests, testing components and hooks, advanced testing techniques, and best practices for react unit testing in app development.

Let's start this quick guide without waiting a second.

Before going ahead, you can read more about:

Difference between Unit Testing and Integration Testing

Learn about the key differences between unit testing and integration testing in software development, including what they are, why they are important, and their benefits. Discover the best tools and testing approaches to rectify any confusion you may have between the two testing methods.

favicon quokkalabs.com

Creating a React Unit Testing - Step-by-Step

Setting Up the Environment

Before we can start writing React unit tests, we must set up our testing environment. It involves installing a React testing library and configuring the testing environment.

A popular React testing library is Jest, which Facebook maintains. To install Jest, you can use npm or yarn:

npm install --save-dev Jest 
Enter fullscreen mode Exit fullscreen mode

Or,

yarn add --dev Jest 
Enter fullscreen mode Exit fullscreen mode

So, next, we need to configure the testing environment. It typically involves creating a "jest.config.js" file in the project's root and specifying the configuration options. For example, you can specify the test environment, test file extension, and test command:

module.exports = { 
  testEnvironment: 'jsdom', 
  testMatch: ['**/__tests__/**/*.test.js'], 
  testCommand: 'jest' 
}; 
Enter fullscreen mode Exit fullscreen mode

In addition to Jest, we also need to install some test utility functions to make our React unit tests easier to write. A popular library for this is "@testing-library/react." You can install it with the below-listed command:

npm install --save-dev @testing-library/react
Enter fullscreen mode Exit fullscreen mode

With the React testing library and test utility functions installed; we are now ready to start writing tests. The following section will cover how to write a simple test in React.

Once you have installed the necessary React testing library and set up your testing environment, you can start writing your tests. To do it, you must create a "*.test.js" file in your project and import the necessary dependencies. For example,

import React from 'react'; 
import { render } from '@testing-library/react'; 
// your component that you want to test 
import MyComponent from './MyComponent'; 
describe('MyComponent', () => { 
  // test case 
  it('renders correctly', () => { 
    const { getByText } = render(<MyComponent />); 
    expect(getByText('Hello, World!')).toBeInTheDocument(); 
  }); 
});
Enter fullscreen mode Exit fullscreen mode

In this example, we are using Jest's "describe" and "it" functions to structure our test. The "describe" function is used to group related tests, and the "it" function defines a single test case. The "render" function from "@testing-library/react" is used to render the component under test, and the "getByText" function is used to retrieve elements from the rendered component based on their text content.

Once you have written your tests, you can run them using the npm test or yarn test command. Jest will then execute your tests and provide a report of the results, including any failed tests.

Setting up the testing environment for React is the first step in writing unit tests. Installing the necessary React testing library and configuring the background ensures that your tests will run smoothly and accurately.

Writing a Simple Unit Test in React

When writing tests for a React component, we typically structure them using Jest's "describe" and "it" functions. The "describe" function is used to group related tests, while the "it" function defines a single test case.

Here's an example of the structure of a simple test:

describe('MyComponent', () => { 
  it('does something', () => { 
    // Test logic goes here 
  }); 
}); 
Enter fullscreen mode Exit fullscreen mode

This example defines a test for a component called "MyComponent." The "it" function describes a single test case, checking that the element "does something."

Testing a React Component

We first need to render a React component using a react testing library such as "@testing-library/react" to test a React component. We can then make assertions about the component's behavior and output using Jest's "expect" function and various test matchers.

Here's an example of testing a React component:

import React from 'react'; 
import { render } from '@testing-library/react'; 
import MyComponent from './MyComponent'; 
 describe('MyComponent', () => { 
  it('renders correctly', () => { 
    const { getByText } = render(<MyComponent />); 
    expect(getByText('Hello, World!')).toBeInTheDocument(); 
  }); 
}); 
Enter fullscreen mode Exit fullscreen mode

In this sample, we are testing the "MyComponent" component. The render function is used to render the part, and the "getByText" function retrieves elements from the rendered piece based on their text content. Finally, the "expect" function asserts the component's behavior. In this case, we are announcing that the document's text, "Hello, World!" is present.

Test Matchers

Test matchers are used with the "expect" function to make assertions about a component's behavior and output. Jest provides a wide range of test matchers to check various conditions, such as whether a value is equal to a specific value, whether an element is present in the document, and more.

Here are some standard test matchers that you might use when testing a React component:

  • toBe: Checks that a value is equal to a specific value.
  • toBeInTheDocument: Checks that an element is present in the document.
  • toHaveTextContent: Checks that a part has certain text content.

Debugging Failed Tests

If a test fails, Jest will provide an error message indicating why the test failed. This message can be used to help diagnose the issue and determine what needs to be fixed.

In addition to the error message, you can use the debug function from @testing-library/react to inspect the rendered component and view its state. It can be especially valuable when determining why a test has failed.

Here's an example of using the debug function to inspect a rendered component:

import React from 'react'; 
import { render, debug } from '@testing-library/react'; 
import MyComponent from './MyComponent'; 
describe('MyComponent', () => { 
  it('renders correctly', () => { 
    const { getByText } = render(<MyComponent />); 
    const text = getByText('Hello, World!'); 
    expect(text).toBeInTheDocument(); 

    // If the test fails, use debug to inspect the component 
    if (!text) { 
      debug(); 
    } 
  }); 

}); 

Enter fullscreen mode Exit fullscreen mode

In this example if the test fails and the text "Hello, World!" is not found in the document, the debug function is called to inspect the rendered component. It allows you to see the component's state and determine what might be causing the issue.

Using the techniques discussed in this section, you can write simple and practical tests for your React components and diagnose and debug any issues.

If you are still struggling with debugging, I suggest you should reach to a quality assurance provider.

Testing React Components

Testing State and Props

In addition to testing the output of a React component, it's also essential to test its state and props. It allows you to verify that the component behaves as expected in response to changes in its shape and props.

To test a component's state and props, you can use the render function from @testing-library/react to render the component with specific props and state and then make assertions about the component's behavior and output.

Here's an example of testing a component's state and properties:

import React from 'react'; 

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



import MyComponent from './MyComponent'; 



describe('MyComponent', () => { 

  it('renders with the correct props and state', () => { 

    const props = { 

      name: 'John Doe', 

    }; 

    const { getByText } = render(<MyComponent {...props} />); 

    expect(getByText(`Hello, ${props.name}!`)).toBeInTheDocument(); 

  }); 

}); 

Enter fullscreen mode Exit fullscreen mode

In this example, the "MyComponent" component is rendered with specific props, and the "expect" function is used to assert the component's behavior and output.

Testing Event Handlers

React components often include event handlers that respond to user interactions, such as clicking a button. To test these event handlers, you can use the "fireEvent" function from @testing-library/react to simulate user interactions and make assertions about the component's behavior and output.

import React from 'react'; 
import { render, fireEvent } from '@testing-library/react'; 
import MyComponent from './MyComponent'; 
describe('MyComponent', () => { 
  it('responds to button clicks', () => { 
    const { getByText } = render(<MyComponent />); 
    const button = getByText('Click Me'); 
    fireEvent.click(button); 
    expect(getByText('You clicked the button!')).toBeInTheDocument(); 
  }); 
}); 

Enter fullscreen mode Exit fullscreen mode

Testing Components with External APIs

React components often interact with external APIs, such as web services or databases. To test these components, you can use a library such as jest-fetch-mock to mock the API calls and verify that the component behaves as expected.

import React from 'react'; 
import { render, waitForElement } from '@testing-library/react'; 
import 'jest-fetch-mock'; 
import MyComponent from './MyComponent'; 
describe('MyComponent', () => { 
  it('fetches data from an API', async () => { 
    fetchMock.mockResponseOnce(JSON.stringify({ data: 'Hello, World!' })); 
    const { getByText } = render(<MyComponent />); 
    await waitForElement(() => getByText('Hello, World!')); 
expect(fetchMock).toHaveBeenCalled(); 
expect(getByText('Hello, World!')).toBeInTheDocument(); 
  }); 
}); 
Enter fullscreen mode Exit fullscreen mode

Testing Components with Redux

React components often use the Redux state management library to manage their state. To test these components, you can use a library such as redux-mock-store to create a mock store and verify that the part dispatches the correct actions and updates the store as expected.

import React from "react"; 
import { render, fireEvent } from "@testing-library/react"; 
import { Provider } from "react-redux"; 
import configureMockStore from "redux-mock-store"; 
import MyComponent from "./MyComponent"; 
import rootReducer from "./rootReducer"; 
const mockStore = configureMockStore([]); 
describe("MyComponent", () => { 
  it("dispatches actions and updates the store", () => { 
    const store = mockStore({}); 
    const { getByText } = render( 
      <Provider store={store}> 
        <MyComponent /> 
      </Provider> 
    ); 
    const button = getByText("Click Me"); 
    fireEvent.click(button); 
    const actions = store.getActions(); 
    expect(actions).toEqual([{ type: "CLICK_BUTTON" }]); 
  }); 
}); 

Enter fullscreen mode Exit fullscreen mode

Testing React Hooks

React Hooks are a new feature introduced in React 16.8 that allows developers to add state and other logic to their functional components. They provide a more convenient and flexible way of managing state and side effects compared to class components.

Testing Custom Hooks

Custom hooks are functions that encapsulate logic and can be reused across multiple components. They can also be tested in isolation, just like regular JavaScript functions.

import { useState } from 'react';  
function useCounter() { 
  const [count, setCount] = useState(0); 
  function increment() { 
    setCount(count + 1); 
  } 
  return { count, increment }; 
} 
describe('useCounter', () => { 
  it('increments the count', () => { 
    const { count, increment } = useCounter(); 
    expect(count).toBe(0); 
    increment(); 
    expect(count).toBe(1); 
  }); 
}); 

Enter fullscreen mode Exit fullscreen mode

Testing Stateful Hooks

To test stateful hooks in React components, you can use a library such as ‘’@testing-library/react’’ to render the component and access its state and functions.

import React from 'react'; 
import { render, fireEvent } from '@testing-library/react'; 
import Counter from './Counter'; 
describe('Counter', () => { 
  it('increments the count', () => { 
    const { getByText } = render(<Counter />); 
    const button = getByText('+'); 
    fireEvent.click(button); 
    const count = getByText('1'); 
    expect(count).toBeInTheDocument(); 
  }); 
}); 
Enter fullscreen mode Exit fullscreen mode

Testing Effect Hooks

Effect hooks perform side effects in React components, such as fetching data or updating the document title. To test these hooks, you can use a library such as jest-fetch-mock to mock the API call, verify that the hook makes the expected call, and update the component's state as expected.

import React, { useEffect, useState } from 'react'; 
import { render, waitForElement } from '@testing-library/react'; 
import fetchMock from 'jest-fetch-mock'; 
function FetchData() {const [data, setData] = useState(null); 
 useEffect(() => { 
    fetch('https://api.example.com/data') 
      .then(response => response.json()) 
      .then data => setData(data)); 
}, []); 
return data ? <div>{data}</div> : <div>Loading...</div>; 
} 
describe('FetchData', () => { 
beforeEach(() => { 
fetchMock.mockResponse(JSON.stringify({ data: 'test data' })); 
}); 
it('fetches and displays data', async () => { 
  const { getByText } = render(<FetchData />); 
  const element = await waitForElement(() => getByText('test data')); 
  expect(fetchMock.mock.calls.length).toBe(1); 
  expect(element).toBeInTheDocument(); 
  }); 
});
Enter fullscreen mode Exit fullscreen mode

Advanced Testing Techniques

Testing Performance

Performance testing is an important aspect of writing unit tests, as it ensures that your components render efficiently and do not impact the overall performance of your application. There are a bunch of techniques and tools you can get the help of to test the performance of your React components, including

  • Measuring the time it takes to render a component with "performance.now()"
  • Using the React Profiler API to measure the time spent rendering components and identify performance bottlenecks
  • Using the lighthouse library to test the performance of your pages, including their loading speed and overall performance score

Testing Snapshots

Snapshot testing is a technique for testing the structure and output of your React components. The idea behind snapshot testing is to take a "snapshot" of the component's output and compare it to the previous snapshot to see if anything has changed.

Snapshot testing can be used to catch unexpected changes in the output of your components, such as changes in the structure of your HTML or changes in the values of your component's props. Jest, the popular JavaScript testing library, has built-in support for snapshot testing.

Testing Component with Context

The capability of context in React to circulate information and functions throughout the component tree without the need for manual prop transmission at every step is a valuable attribute. Testing components that use context can be challenging, but there are a few techniques you can use to make it easier:

  • Using the "TestRenderer" from React testing library to render the component and access its context
  • Creating a test-specific context provider that can be used to pass context data to your component during testing

Testing Error Boundaries

Error boundaries in React offer the ability to manage and recover from mistakes in your components. Evaluating the efficiency of error boundaries involves creating simulated errors and confirming that the error boundary component appropriately handles and bounces back from the error.

You can simulate errors by throwing exceptions in your component or using the "act" function from React testing library to simulate the mistakes during rendering. You can then use "expect" or other test assertion functions to verify that your error boundary component correctly handles and recovers from the error.

React Unit Testing Best Practices

Writing Clean and Readable Tests

Writing clean and readable tests is crucial for ensuring the maintainability and reliability of your test suite. Some best practices for writing clean and readable tests include the following:

  • Writing tests that are self-contained and don't depend on external data or state
  • Using descriptive and clear test names that accurately reflect the purpose of each test
  • Keeping tests small and focused, only testing a single behavior or feature at a time
  • Using assertions and matchers that are clear and easy to understand

Reusability of Tests

The capability to use tests repeatedly is a vital aspect of writing unit tests, as it enables you to write tests once and apply them in various locations across your codebase. Some recommended techniques for enhancing the repeatability of your tests include:

  • Writing tests that are independent of specific components or functions so that they can be reused in different parts of your codebase
  • Extracting common test logic into utility functions or custom matchers to make it easier to reuse your tests

Automation of Tests

Automating your tests is crucial for ensuring that they run quickly and accurately every time you make changes to your code. Some best practices for automating your tests include the following:

  • Running tests automatically as part of your build process using a CI/CD tool
  • Running tests on every push to your codebase using a Git hook or other automation tool
  • Automatically generating test coverage reports to see which parts of your code are and are not being tested

Maintenance of Tests

Maintenance is an essential part of writing unit tests, as it ensures that your tests continue to be accurate and valuable as your code changes over time. Some best practices for maintaining your tests include

  • Regularly review and update your tests to ensure they reflect the current state of your code
  • Deleting or updating tests that are no longer relevant or accurate
  • Keeping your test code up-to-date with the latest react testing library and best practices

Final Words

In this guide, I've covered the basics of how to write unit tests in React. From setting up your testing environment to testing React components and hooks and using advanced testing techniques, you should now understand how to write and maintain high-quality unit tests for your React applications.

But in difficult and complex projects, you may need a better-helping hand in react unit testing. In this scene, you should get the help of a Quality analyst, or you can hire an experienced React developer.

Top comments (0)