Testing React Function Components with Hooks

Testing React Function Components with Hooks

Testing by shallow rendering React Function Components having state and lifecycle hooks using Jest testing library and enzyme by simulating...Testing React Function Components with Hooks

If possible, you should separate your stateful logic and your view. You would normally do this by having Redux-style reducers and stateless components. Both of these are quite easy to test and can make the biggest part of your application.

However, sometimes you probably still want to test a stateful component or simply do not want to refactor to use reducers. Especially when state changes happen asynchronously, things get tricky. In this article I will tell you how you can test various cases of stateful components, using React 16, Jest, and Enzyme.

Creating a Sample Application

Let’s start by creating a sample application using create-react-app

npx create-react-app react16-testing

We can easily run tests now by running npm run test. Afterward, we edit src/App.js and add a minimal example for our testing purposes. We create a functional component, which uses the useState hook.

import React, { useState } from 'react';
import './App.css';

function App() {
  const [color, changeColor] = useState('blue');

  return (
    <div className="App">
      <button onClick={changeColor.bind(null, 'blue')}>blue</button>
      <button onClick={changeColor.bind(null, 'green')}>green</button>
      <button onClick={changeColor.bind(null, 'red')}>red</button>

      <div className={`box ${color}`}>
        Lorem Ipsum
      </div>
    </div>
  );
}

export default App;

App.js

To have some visual feedback, add the following lines to your app.css.

.box { color: white; }
.blue { background-color: blue; }
.red { background-color: red; }
.green { background-color: green; }

The above app will simply display a 3x button elements and 1x div with a background-color, depending on which button you clicked.

Installing Testing Dependencies

Let’s assume we want to test if the component displays the correct class after clicking the button. We will use shallow() rendering API from Enzyme to do so. You can install related dependencies with:

npm install --save enzyme enzyme-adapter-react-16 react-test-renderer

Afterward, you have to create ./src/setupTests.js with some initial commands, which should run before testing starts.

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });

Before React 16, we had to use class components if we wanted to use state. Back then, you would shallow render the component you want to test and could call setState() directly on that component to test if the component behaves correctly on state change. With the introduction of Hooks in functional components, this is not possible anymore.

Testing Function Component With Hooks Using Enzyme

What you could still do, however, is to trigger the related event, which changes the state, manually. Here is an example: App.test.js, which clicks the last button to trigger a state change and a re-render.

import React from 'react';
import { shallow } from 'enzyme';
import App from './App';

it('button changes color of box', () => {
  const app = shallow(<App />);
  expect(app.find('.box').length).toEqual(1);
  app.find('button').last().simulate('click');
  expect(app.find('.box.red').length).toEqual(1);
});

App.test.js

You often want to use the default event to access data attributes as shown in the following example instead of binding values to the function directly.

import React, { useState } from 'react';
import './App.css';

function App() {
  const [color, changeColor] = useState('blue');

  function handleClick(e) {
    changeColor(e.target.getAttribute('data-color'));
  }

  return (
    <div className="App">
      <button data-color='blue' onClick={handleClick}>blue</button>
      <button data-color='green' onClick={handleClick}>green</button>
      <button data-color='red' onClick={handleClick}>red</button>

      <div className={`box ${color}`}>
        Lorem Ipsum
      </div>
    </div>
  );
}

export default App;

App.js

After doing this, our test will fail because our simulate function does not pass an event by default. We have to mock it (see the example below).

import React from 'react';
import { shallow } from 'enzyme';
import App from './App';

it('button click changes color of box', () => {
  const app = shallow(<App />);
  expect(app.find('.box').length).toEqual(1);
  // cache button element
  const button = app.find('button').last();
  // pass mocked event object
  button.simulate('click', {
    target: {
      getAttribute: function() {
        return button.props()['data-color']
      }
    }
  });

  expect(app.find('.box.red').length).toEqual(1);
});

App.test.js

Testing Function Component With Async State

Now, in real-world application, your state changes will probably be triggered asynchronously. If our state change within handleClick happens async, like in the example below, our test will fail too.

function handleClick(e) {
    setTimeout(() => {
        changeColor(e.target.getAttribute('data-color'));
    }, 1000);
}

In this example, the state will change after one second and the test is finished before the re-render even happened. What we could do here is to return a Promise in handleClick, which is resolved after the state change. In our App.test.js, we can then use async/await to wait for the state change to happen. For more details, check the jestjs async tutorial.

function handleClick(e) {
    const node = e.target;
    return new Promise(resolve => {
        setTimeout(() => {
            changeColor(node.getAttribute('data-color'));
            resolve();
        }, 1000);
    });
}
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';

it('button click changes color of box', async () => {
  const app = shallow(<App />);
  expect(app.find('.box').length).toEqual(1);
  // cache button element
  const button = app.find('button').last();
  const eventMock = {
    target: {
      getAttribute: function() {
        return button.props()['data-color']
      }
    }
  };
  // pass mocked event object
  await button.props().onClick(eventMock);
  expect(app.find('.box.red').length).toEqual(1);
});

App.test.js

Make Stateful Function Component Testable

So to make a function component with hooks testable, the function which triggers the state change:

  • Has to be available as a prop so we can trigger it manually
  • Needs a mocked event if it uses values from it
  • Has to return a Promise if the state change happens async

reactjs javascript Hooks

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

React hooks - Giới thiệu tổng quát về Hooks

React hooks ra đời đã giúp functional component trở nên powerful hơn bao giờ hết! 😍Trước đây khi cần dùng đến các tính năng của React như state, life cycle thì mình bắt buộc phải dùng class component. Nhưng giờ thì đã khác, có hooks, functional component như hổ mọc thêm cánh, có thể xử lý được state, life cycle và những thứ khác của React một cách êm đềm.

React Hooks Tutorial for Beginners: Getting Started With React Hooks

React hooks tutorial for beginners, learn React hooks step by step: Introduction, useState Hook, useState with previous state, useState with object, useState with array, useEffect Hook, useEffect after render, Conditionally run effects, Run effects only once, useEffect with cleanup, useEffect with incorrect dependency, Fetching data with useEffect, useContext Hook, useReducer Hook, useReducer, Multiple useReducers, useReducer with useContext, Fetching data with useReducer, useState vs useReducer, useCallback Hook, useMemo Hook, useRef Hook

The essential JavaScript concepts that you should understand

The essential JavaScript concepts that you should understand - For successful developing and to pass a work interview

React Hooks: useState - Part 2

React Hooks: useState. In this blog, we are going to discuss the useState hook in detail. As this hook let us use state without writing a class. This hook is the equivalent of this.state/this.setState for functional components.

Getting Started with React Hooks

Getting Started with React Hooks. When I first started learning React, there was one thing I knew for sure: If you want to use its state and lifecycle methods, you need to use class components — not functional components. According to the docs, hooks let you use state and other React features without writing a class.