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') );
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.
Todos component contains all the state, it needs the
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
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.
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__ setup, out of the box Jest also supports finding any
.spec.js files too.
As we’ll be testing our state functions, go ahead and create
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
"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
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
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.
How to Test React Components Using Jest