In this article, we’ll take a look at using Jest — a testing framework maintained by Facebook — to test our React components. We’ll look at how we can use Jest first on plain JavaScript functions, before looking at some of the features it provides out of the box specifically aimed at making testing React apps easier.
It’s worth noting that Jest isn’t aimed specifically at React: you can use it to test any JavaScript applications. However, a couple of the features it provides come in really handy for testing user interfaces, which is why it’s a great fit with React.
Sample Application
Before we can test anything, we need an application to test! Staying true to web development tradition, I’ve built a small todo application that we’ll use as the starting point. You can find it, along with all the tests that we’re about to write, on GitHub. If you’d like to play with the application to get a feel for it, you can also find a live demo online.
The application is written in ES2015, compiled using webpack with the Babel ES2015 and React presets. I won’t go into the details of the build setup, but it’s all in the GitHub repo if you’d like to check it out. You’ll find full instructions in the README on how to get the app running locally. If you’d like to read more, the application is built using webpack, and I recommend “A Beginner’s guide to webpack” as a good introduction to the tool.
The entry point of the application is app/index.js
, which just renders the Todos
component into the HTML:
render(
<Todos />,
document.getElementById('app')
);
The Todos
component is the main hub of the application. It contains all the state (hard-coded data for this application, which in reality would likely come from an API or similar), and has code to render the two child components: Todo
, which is rendered once for each todo in the state, and AddTodo
, which is rendered once and provides the form for a user to add a new todo.
Because the Todos
component contains all the state, it needs the Todo
and AddTodo
components to notify it whenever anything changes. Therefore, it passes functions down into these components that they can call when some data changes, and Todos
can update the state accordingly.
Finally, for now, you’ll notice that all the business logic is contained in app/state-functions.js
:
export function toggleDone(todos, id) {…}
export function addTodo(todos, todo) {…}
export function deleteTodo(todos, id) {…}
These are all pure functions that take the state (which, for our sample app, is an array of todos) and some data, and return the new state. If you’re unfamiliar with pure functions, they’re functions that only reference data they’re given and have no side effects. For more, you can read my article on A List Apart on pure functions and my article on SitePoint about pure functions and React.
If you’re familiar with Redux, they’re fairly similar to what Redux would call a reducer. In fact, if this application got much bigger I would consider moving into Redux for a more explicit, structured approach to data. But for an application this size, you’ll often find that local component state and some well abstracted functions will be more than enough.
To TDD or Not to TDD?
There have been many articles written on the pros and cons of test-driven development, where developers are expected to write the tests first, before writing the code to fix the test. The idea behind this is that, by writing the test first, you have to think about the API you’re writing, and it can lead to a better design. I find that this very much comes down to personal preference and also to the sort of thing I’m testing. I’ve found that, for React components, I like to write the components first and then add tests to the most important bits of functionality. However, if you find that writing tests first for your components fits your workflow, then you should do that. There’s no hard rule here; do whatever feels best for you and your team.
Introducing Jest
Jest was first released in 2014, and although it initially garnered a lot of interest, the project was dormant for a while and not so actively worked on. However, Facebook has invested a lot of effort into improving Jest, and recently published a few releases with impressive changes that make it worth reconsidering. The only resemblance of Jest compared to the initial open-source release is the name and the logo. Everything else has been changed and rewritten. If you’d like to find out more about this, you can read Christoph Pojer’s comment, where he discusses the current state of the project.
If you’ve been frustrated by setting up Babel, React and JSX tests using another framework, then I definitely recommend giving Jest a try. If you’ve found your existing test setup to be slow, I also highly recommend Jest. It automatically runs tests in parallel, and its watch mode is able to run only tests relevant to the changed file, which is invaluable when you have a large suite of tests. It comes with JSDom configured, meaning you can write browser tests but run them through Node. It can deal with asynchronous tests and has advanced features such as mocking, spies and stubs built in.
Installing and Configuring Jest
To start with, we need to get Jest installed. Because we’re also using Babel, we’ll install another couple of modules that make Jest and Babel play nicely out of the box, along with Babel and the required presets:
npm install --save-dev jest babel-jest @babel/core @babel/preset-env @babel/preset-react
You also need to have a babel.config.js
file with Babel configured to use any presets and plugins you need. The sample project already has this file, which looks like this:
module.exports = {
presets: [
'@babel/preset-env',
'@babel/preset-react',
],
};
This article won’t be going into depth on setting up Babel. I recommend the Babel usage guide if you’d like to learn more about Babel specifically.
We won’t install any React testing tools yet, because we’re not going to start with testing our components, but our state functions.
Jest expects to find our tests in a __tests__
folder, which has become a popular convention in the JavaScript community, and it’s one we’re going to stick to here. If you’re not a fan of the __tests__
setup, out of the box Jest also supports finding any .test.js
and .spec.js
files too.
As we’ll be testing our state functions, go ahead and create __tests__/state-functions.test.js
.
We’ll write a proper test shortly, but for now, put in this dummy test, which will let us check everything’s working correctly and we have Jest configured:
describe('Addition', () => {
it('knows that 2 and 2 make 4', () => {
expect(2 + 2).toBe(4);
});
});
Now, head into your package.json
. We need to set up npm test
so that it runs Jest, and we can do that simply by setting the test
script to run jest
:
"scripts": {
"test": "jest"
}
If you now run npm test
locally, you should see your tests run, and pass!
PASS __tests__/state-functions.test.js
Addition
✓ knows that 2 and 2 make 4 (5ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 passed, 0 total
Time: 3.11s
If you’ve ever used Jasmine, or most testing frameworks, the above test code itself should be pretty familiar. Jest lets us use describe
and it
to nest tests as we need to. How much nesting you use is up to you. I like to nest mine so all the descriptive strings passed to describe
and it
read almost as a sentence.
When it comes to making actual assertions, you wrap the thing you want to test within an expect()
call, before then calling an assertion on it. In this case, we’ve used toBe
. You can find a list of all the available assertions in the Jest documentation. toBe
checks that the given value matches the value under test, using ===
to do so. We’ll meet a few of Jest’s assertions through this tutorial.
Testing Business Logic
Now that we’ve seen Jest work on a dummy test, let’s get it running on a real one! We’re going to test the first of our state functions, toggleDone
. toggleDone
takes the current state and the ID of a todo that we’d like to toggle. Each todo has a done
property, and toggleDone
should swap it from true
to false
, or vice-versa.
Note: if you’re following along with this, make sure you’ve cloned the repo and have copied the app
folder to the same directory that contains your ___tests__
folder. You’ll also need to install all the app’s dependencies (such as React). You can make sure it’s all installed by running npm install
once you’ve cloned the repository.
I’ll start by importing the function from app/state-functions.js
, and setting up the test’s structure. Whilst Jest allows you to use describe
and it
to nest as deeply as you’d like to, you can also use test
, which will often read better. test
is just an alias to Jest’s it
function, but can sometimes make tests much easier to read and less nested.
For example, here’s how I would write that test with nested describe
and it
calls:
import { toggleDone } from '../app/state-functions';
describe('toggleDone', () => {
describe('when given an incomplete todo', () => {
it('marks the todo as completed', () => {
});
});
});
And here’s how I would do it with test
:
import { toggleDone } from '../app/state-functions';
test('toggleDone completes an incomplete todo', () => {
});
The test still reads nicely, but there’s less indentation getting in the way now. This one is mainly down to personal preference; choose whichever style you’re more comfortable with.
Now we can write the assertion. First, we’ll create our starting state, before passing it into toggleDone
, along with the ID of the todo that we want to toggle. toggleDone
will return our finish state, which we can then assert on:
import { toggleDone } from "../app/state-functions";
test("tooggleDone completes an incomplete todo", () => {
const startState = [{ id: 1, done: false, text: "Buy Milk" }];
const finState = toggleDone(startState, 1);
expect(finState).toEqual([{ id: 1, done: true, text: "Buy Milk" }]);
});
Notice now that I use toEqual
to make my assertion. You should use toBe
on primitive values, such as strings and numbers, but toEqual
on objects and arrays. toEqual
is built to deal with arrays and objects, and will recursively check each field or item within the object given to ensure that it matches.
With that, we can now run npm test
and see our state function test pass:
PASS __tests__/state-functions.test.js
✓ tooggleDone completes an incomplete todo (9ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 passed, 0 total
Time: 3.166s
Rerunning Tests on Changes
It’s a bit frustrating to make changes to a test file and then have to manually run npm test
again. One of Jest’s best features is its watch mode, which watches for file changes and runs tests accordingly. It can even figure out which subset of tests to run based on the file that changed. It’s incredibly powerful and reliable, and you’re able to run Jest in watch mode and leave it all day whilst you craft your code.
To run it in watch mode, you can run npm test -- --watch
. Anything you pass to npm test
after the first --
will be passed straight through to the underlying command. This means that these two commands are effectively equivalent:
npm test -- --watch
jest --watch
I’d recommend that you leave Jest running in another tab, or terminal window, for the rest of this tutorial.
Before moving on to testing the React components, we’ll write one more test on another one of our state functions. In a real application I would write many more tests, but for the sake of the tutorial, I’ll skip some of them. For now, let’s write a test that ensures that our deleteTodo
function is working. Before seeing how I’ve written it below, try writing it yourself and seeing how your test compares.
Remember that you’ll have to update the import
statement at the top to import deleteTodo
along with toggleTodo
:
import { toggleDone, deleteTodo } from "../app/state-functions";
And here’s how I’ve written the test:
test('deleteTodo deletes the todo it is given', () => {
const startState = [{ id: 1, done: false, text: 'Buy Milk' }];
const finState = deleteTodo(startState, 1);
expect(finState).toEqual([]);
});
The test doesn’t vary too much from the first: we set up our initial state, run our function and then assert on the finished state. If you left Jest running in watch mode, notice how it picks up your new test and runs it, and how quick it is to do so! It’s a great way to get instant feedback on your tests as you write them.
The tests above also demonstrate the perfect layout for a test, which is:
- set up
- execute the function under test
- assert on the results
By keeping the tests laid out in this way, you’ll find them easier to follow and work with.
Now that we’re happy testing our state functions, let’s move on to React components.
Testing React Components
It’s worth noting that, by default, I would actually encourage you to not write too many tests on your React components. Anything that you want to test very thoroughly, such as business logic, should be pulled out of your components and sit in standalone functions, just like the state functions that we tested earlier. That said, it is useful at times to test some React interactions (making sure a specific function is called with the right arguments when the user clicks a button, for example). We’ll start by testing that our React components render the right data, and then look at testing interactions.
To write our tests, we’ll install Enzyme, a wrapper library written by Airbnb that makes testing React components much easier.
Note: since this article was first written, the React team has shifted away from Enzyme and instead recommends React Testing Library (RTL). It’s worth having a read of that page. If you’re maintaining a codebase that has Enzyme tests already, there’s no need to drop everything and move away, but for a new project I’d recommend considering RTL.
Along with Enzyme, we’ll also need to install the adapter for whichever version of React we’re using. For React v16, this would be enzyme-adapter-react-16
, but for React v17 there’s currently no official adapter available, so we’ll have to use an unofficial version. Please note that this package is intended as a stop-gap until official support is released and will be deprecated at that time.
You can follow the progress on an official version in this GitHub issue.
npm install --save-dev enzyme @wojtekmaj/enzyme-adapter-react-17
There’s a small amount of setup that we need for Enzyme. In the root of the project, create setup-tests.js
and put this code in there:
import { configure } from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
configure({ adapter: new Adapter() });
We then need to tell Jest to run this file for us before any tests get executed. We can do that by configuring the setupFilesAfterEnv
option. You can put Jest config in its own file, but I like to use package.json
and put things inside a jest
object, which Jest will also pick up:
"jest": {
"setupFilesAfterEnv": [
"./setup-tests.js"
]
}
Now we’re ready to write some tests! Let’s test that the Todo
component renders the text of its todo inside a paragraph. First we’ll create __tests__/todo.test.js
, and import our component:
import Todo from '../app/todo';
import React from 'react';
import { mount } from 'enzyme';
test('Todo component renders the text of the todo', () => {
});
I also import mount
from Enzyme. The mount
function is used to render our component and then allow us to inspect the output and make assertions on it. Even though we’re running our tests in Node, we can still write tests that require a DOM. This is because Jest configures jsdom, a library that implements the DOM in Node. This is great because we can write DOM-based tests without having to fire up a browser each time to test them.
We can use mount
to create our Todo
:
const todo = { id: 1, done: false, name: 'Buy Milk' };
const wrapper = mount(
<Todo todo={todo} />
);
And then we can call wrapper.find
, giving it a CSS selector, to find the paragraph that we’re expecting to contain the text of the Todo. This API might remind you of jQuery, and that’s by design. It’s a very intuitive API for searching rendered output to find the matching elements.
const p = wrapper.find('.toggle-todo');
And finally, we can assert that the text within it is Buy Milk
:
expect(p.text()).toBe('Buy Milk');
Which leaves our entire test looking like so:
import Todo from '../app/todo';
import React from 'react';
import { mount } from 'enzyme';
test('TodoComponent renders the text inside it', () => {
const todo = { id: 1, done: false, name: 'Buy Milk' };
const wrapper = mount(
<Todo todo={todo} />
);
const p = wrapper.find('.toggle-todo');
expect(p.text()).toBe('Buy Milk');
});
And now we have a test that checks we can render todos successfully.
Next, let’s look at how you can use Jest’s spy functionality to assert that functions are called with specific arguments. This is useful in our case, because we have the Todo
component that’s given two functions as properties, which it should call when the user clicks a button or performs an interaction.
In this test, we’re going to assert that when the todo is clicked, the component will call the doneChange
prop that it’s given:
test('Todo calls doneChange when todo is clicked', () => {
});
We want to have a function that we can use to keep track of its calls, and the arguments that it’s called with. Then we can check that, when the user clicks the todo, the doneChange
function is called and also called with the correct arguments. Thankfully, Jest provides this out of the box with spies. A spy is a function whose implementation you don’t care about; you just care about when and how it’s called. Think of it as you spying on the function. To create one, we call jest.fn()
:
const doneChange = jest.fn();
This gives a function that we can spy on and make sure it’s called correctly. Let’s start by rendering our Todo
with the right props:
const todo = { id: 1, done: false, name: 'Buy Milk' };
const doneChange = jest.fn();
const wrapper = mount(
<Todo todo={todo} doneChange={doneChange} />
);
Next, we can find our paragraph again, just like in the previous test:
const p = wrapper.find(".toggle-todo");
And then we can call simulate
on it to simulate a user event, passing click
as the argument:
p.simulate('click');
And all that’s left to do is assert that our spy function has been called correctly. In this case, we’re expecting it to be called with the ID of the todo, which is 1
. We can use expect(doneChange).toBeCalledWith(1)
to assert this — and with that, we’re done with our test!
test('TodoComponent calls doneChange when todo is clicked', () => {
const todo = { id: 1, done: false, name: 'Buy Milk' };
const doneChange = jest.fn();
const wrapper = mount(
<Todo todo={todo} doneChange={doneChange} />
);
const p = wrapper.find('.toggle-todo');
p.simulate('click');
expect(doneChange).toBeCalledWith(1);
});
Conclusion
Facebook released Jest a long time ago, but in recent times it’s been picked up and worked on excessively. It has fast become a favorite for JavaScript developers and it’s only going to get better. If you’ve tried Jest in the past and not liked it, I can’t encourage you enough to try it again, because it’s practically a different framework now. It’s quick, great at rerunning specs, gives fantastic error messages, and has a great expressive API for writing good tests.
If you enjoyed this article, you might also find the following useful:
- Top React Testing Libraries in 2023: A Comprehensive Review
- Cypress Testing: A Guide to Running Web Application Tests
- An Introduction to Python Unit Testing with unittest and pytest
- Learn End-to-end Testing with Puppeteer
- 3 Methods for Hands-free Continuous Testing
- Re-Introducing Jenkins: Automated Testing with Pipelines
FAQs About Testing React Components Using Jest
What Is render in Jest?
In Jest, the render
function is part of the testing utilities provided by the @testing-library/react
library, which is often used for testing React components. The render
function is used to render a React component into a virtual DOM environment so that you can interact with and make assertions about the rendered output in your tests.
What is React Test Renderer?
React Test Renderer is a built-in React library that provides a simple and lightweight way to render React components into a format that can be easily inspected and tested. It’s part of the React library itself and is often used for testing and snapshot testing in particular.
How do you test a component with Jest in React?
To test a component with Jest in React, you’ll follow a structured process. Firstly, ensure Jest and React Testing Library are installed as dev dependencies. Then, create a dedicated test file adjacent to your component, typically named with the component’s name followed by .test.js
, .test.jsx
, .test.ts
, or .test.tsx
. Import the necessary dependencies, including the component you intend to test, React Testing Library functions, and any required utilities.
Within your test file, write test cases using Jest’s test
or it
functions. These test cases encapsulate specific scenarios you want to verify in your component. Employ React Testing Library to render your component, simulate user interactions if necessary, and make assertions about its behavior and rendered output. Employ Jest’s matchers, such as expect
, to specify your assertions. When ready, run your tests with the jest
command, and Jest will execute the tests and provide feedback on their success or failure.
For instance, consider a simple example where you’re testing a React component named MyComponent
to ensure it renders a given message correctly. In your test file, you import render
from React Testing Library, render MyComponent
with a specific message prop, and then assert that the expected message is present in the rendered output. This structured approach to testing helps ensure that your React components behave as intended and produce the expected results, promoting code reliability and maintainability.
How do you test components with props in Jest?
Testing components with props in Jest involves a systematic approach to ensure that your React components behave as expected with different prop configurations. Begin by importing the necessary dependencies, including React, React Testing Library functions, and the component you intend to test. Next, write individual test cases using Jest’s test
or it
functions, each focusing on a specific aspect or behavior of your component.
Within each test case, render your component with various props configurations to cover different scenarios. Utilize assertions, such as Jest’s expect
and its matchers, to validate whether the rendered component behaves correctly based on the provided props. Be sure to test different values and combinations of props to verify that your component responds appropriately.
Additionally, consider testing prop types or type checking if your component specifies PropTypes or uses TypeScript. Ensure that your component gracefully handles both valid and invalid prop values, as well as missing props, to prevent potential runtime errors. If your component involves user interactions, simulate these interactions using React Testing Library functions like fireEvent
to guarantee that the component responds as expected to user input.
Running your tests with the jest
command will provide feedback on the success or failure of your test cases. By thoroughly testing components with props, you can have confidence that they handle diverse prop scenarios correctly, leading to more robust and reliable React applications.
How to test a function inside a React component with Jest?
Testing a function inside a React component with Jest and React Testing Library involves several key steps. Firstly, you should import the necessary testing dependencies, including Jest, React Testing Library functions (like render
and fireEvent
), and your React component that contains the function you want to test.
After importing the dependencies, you can render your React component using render
from React Testing Library. This step allows you to obtain access to the rendered component instance. To access the specific function inside the component, you need to locate the relevant element or component that triggers the function. You can use queries like getByTestId
, getByRole
, or getByText
to access elements in the rendered output.
Once you’ve accessed the component or element, you can call the function directly and pass any required arguments. After invoking the function, you can use Jest’s expect
and matchers to assert the expected behavior of the function. For example, you can check if the function correctly updates state, triggers side effects, or returns the expected values.
I'm a JavaScript and Ruby Developer working in London, focusing on tooling, ES2015 and ReactJS.