How to approach unit testing in a React application

Introduction

React testing is an important part of building and maintaining React applications. The tools we’re going to use are:

  • Jest: A JavaScript testing framework, which provides us with tools to write assertions.
  • React Testing Library: A set of helper functions for testing React components. It encourages best practices by making it difficult to write tests that break when you refactor your components.

First, you need to install Jest and React Testing Library. Here’s how you do it with npm:

npm install --save-dev jest @testing-library/react

Writing Your First Test

Let’s say you have a simple component like this:

// Greeting.js

import React from 'react';

const Greeting = ({ name }) => (
  <h1>Hello, {name}!</h1>
);

export default Greeting;

To test this component, you might write a test like this:

// Greeting.test.js

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

test('renders greeting with name', () => {
  render(<Greeting name="John" />);
  expect(screen.getByText('Hello, John!')).toBeInTheDocument();
});

This test uses the render function from React Testing Library to render the component, and then uses screen.getByText to find the element with the text “Hello, John!”. The expect function is from Jest, and toBeInTheDocument is a Jest matcher from ‘@testing-library/jest-dom’.

Five Examples of Unit Tests
1. Testing Prop Changes
// Counter.js

import React, { useState } from 'react';

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};

export default Counter;

// Counter.test.js

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

test('renders with initial count', () => {
  render(<Counter initialCount={5} />);
  expect(screen.getByText('Count: 5')).toBeInTheDocument();
});

2. Testing User Interaction

Consider a component Button.js:

//button.js

import React from 'react';

const Button = ({ onClick, label }) => (
  <button onClick={onClick}>{label}</button>
);

export default Button;
//test_button.js

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

test('Button has correct label and responds to click event', () => {
  const handleClick = jest.fn();
  const { getByText } = render(<Button onClick={handleClick} label="Click me" />);
  
  fireEvent.click(getByText('Click me'));
  
  expect(getByText('Click me')).toBeInTheDocument();
  expect(handleClick).toHaveBeenCalledTimes(1);
});

In this test, we render the Button component with the text “Click me”, simulate a click event, and verify that the button was clicked exactly once and the text “Click me” is in the document.

3. Testing Conditional Rendering

// ConditionalGreeting.js

import React from 'react';

const ConditionalGreeting = ({ isLoggedIn }) => (
  isLoggedIn ? <p>Welcome back!</p> : <p>Please sign in.</p>
);

export default ConditionalGreeting;

// ConditionalGreeting.test.js

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

test('renders welcome back when logged in', () => {
  render(<ConditionalGreeting isLoggedIn={true} />);
  expect(screen.getByText('Welcome back!# I'll search for a couple more examples to complete the request.
search("React Testing Library examples")

4. Testing Class Changes

You can also test how your component behaves when it receives different kinds of user interaction. In this example, we’re testing that a Link component changes its class when it’s hovered over:

// Link.js

import { useState } from 'react';

const STATUS = {
  HOVERED: 'hovered',
  NORMAL: 'normal',
};

export default function Link({ page, children }) {
  const [status, setStatus] = useState(STATUS.NORMAL);

  const onMouseEnter = () => {
    setStatus(STATUS.HOVERED);
  };

  const onMouseLeave = () => {
    setStatus(STATUS.NORMAL);
  };

  return (
    <a
      className={status}
      href={page || '#'}
      onMouseEnter={onMouseEnter}
      onMouseLeave={onMouseLeave}
    >
      {children}
    </a>
  );
}

// Link.test.js

import renderer from 'react-test-renderer';
import Link from '../Link';

it('changes the class when hovered', () => {
  const component = renderer.create(
    <Link page="http://www.facebook.com">Facebook</Link>,
  );
  let tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});

The test asserts that the initial rendering matches a saved snapshot. The snapshot will contain the output of the Link component given the props specified in the test.

5. Testing Checkbox Interaction

In this example, we’re testing a CheckboxWithLabel component that changes its label when it’s clicked:

// CheckboxWithLabel.js

import { useState } from 'react';

export default function CheckboxWithLabel({ labelOn, labelOff }) {
  const [isChecked, setIsChecked] = useState(false);

  const onChange = () => {
    setIsChecked(!isChecked);
  };

  return (
    <label>
      <input type="checkbox" checked={isChecked} onChange={onChange} />
      {isChecked ? labelOn : labelOff}
    </label>
  );
}

// CheckboxWithLabel.test.js

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

// Note: running cleanup afterEach is done automatically for you in @testing-library/[email protected] or higher
// unmount and cleanup DOM after the test is finished.
afterEach(cleanup);

it('CheckboxWithLabel changes the text after click', () => {
  const { queryByLabelText, getByLabelText } = render(
    <CheckboxWithLabel labelOn="On" labelOff="Off" />,
  );

  expect(queryByLabelText(/off/i)).toBeTruthy();

  fireEvent.click(getByLabelText(/off/i));

  expect(queryByLabelText(/on/i)).toBeTruthy();
});

This test first checks that the “Off” label is displayed, then simulates a click on the checkbox and checks that the “On” label is displayed.

The test can also be written using Enzyme:

// CheckboxWithLabel.test.js

import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import CheckboxWithLabel from '../CheckboxWithLabel';

Enzyme.configure({ adapter: new Adapter() });

it('CheckboxWithLabel changes the text after click', () => {
  // Render a checkbox with label in the document
  const checkbox = shallow(<CheckboxWithLabel labelOn="On" labelOff="Off" />);

  expect(checkbox.text()).toBe('Off');

  checkbox.find('input').simulate('change');

  expect(checkbox.text()).toBe('On');
});

Conclusion

These examples should give you a good starting point for testing React components. As you can see, Jest and the React Testing Library provide powerful tools for simulating user interaction, testing component output, and ensuring that your components continue to work as expected as your application evolves.

Feedback Display