Quick Links

Jest is a framework for running "Unit Tests," a way for you to test the individual functions and components from your codebase to ensure future commits don't break something unexpected. We'll show you how to set it up, and use it with a front-end framework like React.

What Is Unit Testing?

Testing is very important when working with a team of programmers---any commits pushed to your source control should be automatically built and tested to make sure you don't accidentally break anything. To keep everyone sane, the whole pipeline can be automated with software like Jenkins, but for running the actual tests, you'll need to use a unit testing framework like Jest.

Jest is a framework for running "Unit Tests." A "unit" can be any number of things---a function, a class, or a component---it simply represents the smallest unit of code that you should be testing, to make sure future code updates don't break anything. If you write a function that fetches some data from an endpoint and returns some output, you'll write a corresponding unit test that will take this function, call it, and log the output. Then, you can make assertions based on what it returned. Is it formatted properly? Are any values undefined? Is it operating as expected? If any of these tests fail, something is wrong in your code.

Jest can also be used to run tests on web applications. It's commonly used with data-driven frameworks like React to ensure that components have the proper state and props for a given input.

While unit tests can seem tedious to have to write, and it's certainly more work, it usually leads to better codebases in the end. Dealing with legacy code is a problem for exactly this reason---leaving proper comments helps, but writing a unit test is like writing documentation for your functions. You define what input goes in, how to use it, and what input should be expected out.

Getting Started with Jest

To set up a simple testing environment, create a new project with

        npm init -y
    

, and install Jest as a development dependency from

        npm
    

:

npm install --save-dev jest

To run tests from the command line, you'll need to add a simple script in package.json that calls Jest:

"scripts": {

"test": "jest"

}

You'll need a function to test, so create a new file under src/doSomeMath.js, and set it to export a function:

function doSomeMath(a, b) {

return a + b;

}

module.exports = doSomeMath;

Save this file, and open up tests/doSomeMath.test.js. Jest will automatically look in the test/ directory to find tests to run. This file acts as a companion file to doSomeMath.js, and defines a suite of tests to verify that all functions are working properly. A basic Jest test looks something like the following:

test('Description', () => {

  expect(functionName(args)).toBe(result);

});

Basically, a test() function wraps all of the code for the test. Each expect() block calls a function (i.e., a unit), then passes the value to the "matcher." In this case, that's the toBe() function, which checks for simple equality.

Jest has a lot of different matchers, which you can read all about in their docs. To name a few:

  • .not.matcher() handles inversion of any matcher.
  • toBe() checks for exact equality (Object.is) but notably doesn't handle objects.
  • toEqual() checks for deep object equality.
  • toStrictEqual() is the same as toEqual but also makes sure that neither object has additional undefined properties.
  • toBeTruthy() and toBeFalsy() check for anything that evaluates to true or false in an if statement.
  •  toBeNull() , toBeUndefined() , and toBeDefined() all check for different states of objects.
  • toContain() checks array contents.
  • toMatch() matches a regular expression against a string.
  • toThrow() makes sure the code will throw an error (usually to test incorrect input).

For this test suite, you'll need to import your function, and pass in some arguments and results. You can and usually should include multiple expect blocks within a single test to cover multiple scenarios.

const doSomeMath = require('../src/doSomeMath');

test('adds 2 + 2 to equal 4', () => {

expect(doSomeMath(1, 1)).toBe(2);

expect(doSomeMath(2, 2)).toBe(4);

});

Save this file,  then run:

npm run test

This will run all of your test suites, and output the results for each one.

jest tests

Everything should pass, provided math is working correctly. If it fails, Jest will give you a detailed description of what went wrong, helping you to track down the problem.

Because Jest code is just JavaScript, you can script your tests quite easily. For example, you could use a for loop, and call expect multiple times with iterative input. If any execution of expect fails, the test itself will fail.

test('adds 2 + 2 to equal 4', () => {

for (let a = 1; a < 10; a++) {

expect(doSomeMath(a, 5)).toBe(a + 5)

}

});

You can use this functionality for a lot of things, but most tests will usually do three main things:

  • Arrange, where data is prepared for the unit
  • Act, where the unit is called, passed the arranged data, and the output is logged
  • Assert, where you run all of your matchers to make sure everything is working as intended

Testing with React

Making sure math works is cool and all, but you likely aren't intending to use Jest for something as simple as that. You're probably interested in using Jest to automate testing for a JavaScript application, maybe built with a framework like React. While you should still be doing full UI testing with a manual review, Jest can still be used to test React components in isolation. After all, React components are really just functions that return JSX output---this can be matched and tested for.

To get started, set up a new React project with create-react-app:

npx create-react-app jest-react

Create React App installs Jest by default, as well as React Testing Library, which contains some useful handlers for working with React in Jest.

Modify the default App.js component to the following:

import React, {useState} from 'react';

import './App.css';

function App() {

  const [disabled, setDisabled] = useState(false);

  function handleClick() {

    setDisabled(!disabled);

  }

  return (

    <button data-testid="useless-button" disabled={disabled} onClick={handleClick}>

      Click Me

    </button>

  );

}

export default App;

This absolutely useless piece of code defines a button that, once clicked, sets a state variable that is used to disable all further clicks of this button.

Open up App.test.js, which is already created by Create React App to contain a test for the default React logo. Instead, modify the default test to the following:

import React from 'react';

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

import App from './App';

test('disabled button on click', () => {

  const button = render(<App />).getByTestId('useless-button');

  expect(button.disabled).toBeFalsy();

  fireEvent.click(button, {button: 1});

  expect(button.disabled).toBeTruthy();

});

This test renders out the component using React Testing Library's renders, and finds the button based on the test ID we gave it (though, there are many different selectors that won't clutter up your component code). It makes sure the button isn't disabled off the bat, then fires a click event. (Note that {button: 1} is simply the code for left-click.) The state updates, which triggers a rerender, and we check again to verify that the button is now disabled.

It's important to always test the output of functions. Most of the time, you don't exactly care how the function is implemented under the hood, just that it returns the correct output. This component is just a function that returns a button that may or may not be disabled. We don't care that the button uses React's state API under the hood, so we test for whether or not the HTML returned contains a disabled button or not. This isn't to say you can't test for state, but you should consider whether or not it's necessary.