An Easy Guide to Testing React Hooks
In this post, we will be looking at an overview of React hooks and then an easy way to test them independently with a React Hooks testing library.
What are React Hooks?
Hooks in React are basically functional components with the powers of classes, this means that the application and advantages of lifecycle methods can now be accessed in a functional component without being in a class.
Types of Hooks
Some of the hooks that exist in React are UseState for handling simple changes in state and acting like Lifecycle hooks in React class components would act in a functional component. There is also UseEffect used to handle effects like timers, listeners, and persistent connections. UseReducer acts like Redux for complex state changes and you can even create your own custom hooks in React too so these are not the only hooks you have access to.
Why Hooks?
Classes are still very much supported in React but there are few reasons behind the new and shiny kid on the block, hooks. One of which is how difficult it is to re-use stateful logic and all the ways we deal with higher-order components. Another reason is readability, with hooks in place you are guaranteed to write fewer lines of code and thereby impacting readability very positively. Classes can be confusing, a lot of developers still struggle with how ‘this’ works in JavaScript and it still differs from one language to another. You can take a look at the hooks documentation here to get started with using it. A hook looks something like this:
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
You might be new to hooks and wondering how to test them just like you can test other React components. This post will take you through a very easy process.
Testing in React
For a good engineer, testing is important for so many reasons and some of them are to ensure you write quality code that does exactly what you expect it to do. It is also important so you save time debugging later, because more tests means less errors in general. Hooks are tested like other functions are tested in React with robust libraries like:
Jest
Jest is hands down the most popular testing library for React components and it was build by the Jest team at Facebook and used by Uber and even Airbnb to name a few companies. React recommends it as the testing library of choice and it works with JavaScript projects like create-react-app to Vue and even Babel. The UI snapshot testing is probably one of the most amazing things about Jest, it can also do parallel testing based on predefined processes and with optional priority that can be set for failed tests.
Enzyme
Enzyme is a JavaScript testing library that was designed by the team at Airbnb to easily test React components. It lets you do shallow rendering of particular components and access the business implementations of the components. You can also perform DOM rendering with enzyme and even test hooks.
Jasmine
Jasmine is popularly known as the testing framework for Node and browsers. It is a behaviour-driven testing library for JavaScript projects and so it does not necessarily depend on the structure of the project or even the browser. Ut is mostly used for Angular projects but it is great for testing React components too. With Babel and Enzyme, you can test a React component using Jasmine
Today we would use something new and easy to start with.
React hooks testing library
This library was built and maintained by over 35 React community members as a beginner-friendly testing library for React hooks. It has great documentation and anyone can easily follow it to test their Hooks, the contributors did a great job. It allows you to test hooks and provides you with even more utility functions to play with. You might have gotten this error before:
Invariant Violation: Hooks can only be called inside the body of a function component.
But still want to call tests within the body of your functional components, keep reading.
Getting started
The first thing to do is to create a new React project, let us call it my-app so in your terminal navigate to your preferred file location and run these:
npx create-react-app my-app
cd my-app
npm start
Next, you have to install the library and its dependencies so run the following commands:
npm install --save-dev @testing-library/react-hooks
npm install --save-dev react-test-renderer@^16.9.0
What we will build
We are going to build a small counting game that would use the useState React hook to hold state.
Navigate to the root folder and change the content inside the app.js file to the code block below:
import React from 'react';
import {Count} from './Count'
import './App.css';
function App() {
const {counter, add, remove} = Count()
return (
<div className="App">
<h1>
This is a counter game
</h1>
<button onClick={add}>Add one</button>
<button onClick={remove}>Remove one</button>
<h3>{counter}</h3>
</div>
);
}
export default App;
You can see that our app file is a functional component that returns the counting game. However, the logic for the addition and subtraction is being imported into the file. To set that up, create a new file in the same root directory and call it count.js
and copy the code block below into it
import React from 'react';
export const Count = () => {
const [counter, setCounter] = React.useState(0);
const add = ()=>{
setCounter(counter + 1)
};
const remove = ()=>{
setCounter(counter - 1)
};
return { counter, add, remove}
};
You can see that the useState hook is used here to simply hold the state of the counter as it changes by increase or decrease.
Testing the hook
Now that we have the testing library installed we can test for two outcomes
- That the addition actually works
- That the subtraction also works too.
Create a test file that React recognizes and that would be count.test.js and inside it copy the code block below:
import {Count} from './Count'
import {act, renderHook} from '@testing-library/react-hooks'
import expect from 'expect'
describe("Adding one", () => {
it("Adds one to counter", () => {
const { result } = renderHook(Count)
act(()=> {
result.current.add()
})
expect(result.current.counter).toBe(1)
});
})
So we use “describe” to explain what we are to test in one test suite and then have the “it” blocks for each test. Inside the block, you will specify the action which in our case is to add, and then you will state what the expected outcome should be. Just like any logical test, you will preset conditions to be tested. Remember we are testing for two outcomes so go back to your count.js and update the code block to contain the subtract side of the test like this:
import {Count} from './Count'
import {act, renderHook} from '@testing-library/react-hooks'
import expect from 'expect'
describe("Adding one", () => {
it("Adds one to counter", () => {
const { result } = renderHook(Count)
act(()=> {
result.current.add()
})
expect(result.current.counter).toBe(1)
});
it("Removes one from counter", () => {
const { result } = renderHook(Count)
act(()=> {
result.current.remove()
})
expect(result.current.counter).toBe(-1)
})
})
If you want to run the test, you can have to use a command for your specific package manager, if NPM
npm test
and if you use Yarn then:
yarn test
If you did everything correctly, your two tests should pass like mine.
Wrapping up
In this article, you have been given a brief overview of React Hooks and how important they can be. You have also been taken through easily testing your React hooks with the React hooks testing library.
Open Source Session Replay
OpenReplay is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data.
Start enjoying your debugging experience - start using OpenReplay for free.