How to write unit tests in JavaScript with Jest
Tags: javascript, tutorial, testing, webdev
Published: 2021-07-24 11:39:48

Unit testing is an important and often overlooked part of the development process. It is considered boring by many, and being traditionally difficult to properly set up earned it a poor reputation early on. The benefits of shipping quality code certainly outweigh any negatives, but how does one find the time and muster the effort to start writing unit tests?

Lucky for us, writing unit tests in JavaScript has never been faster, easier, and arguably more fun thanks to Jest.

[Jest](https://jestjs.io/) is a feature-rich JavaScript testing framework that aims to bring testing to the masses. It's near-zero configuration approach makes it simple to set up, and a familiar API makes writing tests fairly straightforward.

This article will provide a brief introduction into Jest and the concepts behind unit testing. We will learn how to install Jest, write test suites with test cases and fixtures, and run tests both with and without coverage reports. 

We will assume that we're testing a module containing a simple function behaving as a validation rule. The rule checks whether the validated value is an integer number. For example:

```jsx
// isInteger.js
module.exports = (value) => !isNaN(parseInt(value, 10));
```

This implementation is naive and faulty on purpose. We want to see what our tests will teach us about the flaws in our code by passing and failing test cases. Fixing the implementation is not covered by this article, but feel free to play with it as we move through it.

Read on to find out more!

---

## What is a unit test?

A **unit test** is an automated test of a unit of source code. A unit test asserts if the unit's behaviour matches expectations.

A unit is usually a line of code, function, or class. There is no strict definition of what makes up a unit, but it's common to start with whatever seems "smallest".

Units that have no dependencies are called **isolated (solitary) units**. Units that have dependencies are called **sociable units**.

Solitary units are easy to test, but sociable units are more difficult. The output of a sociable unit depends on other units of code - if other units fail, the tested unit fails as well. This created two unit test styles: sociable unit tests and solitary unit tests.

**Sociable unit tests** fail if the dependencies of a sociable unit are also failing. The tested unit is not supposed to work if it's dependencies don't work, so a failing test in this case is a good sign.

**Solitary unit tests** isolate sociable units by creating **mock implementations** of their dependencies. Mocks control how dependencies behave during tests, making sociable units predictable to test.

No matter the unit test style, the goal of unit testing remains the same - to **ensure that individual parts of the program are working correctly as expected**.

---

## What is Jest?

**[Jest](https://jestjs.io/)** is a JavaScript testing framework designed to make testing as easy as possible. It provides all the essential tools for running tests, making assertions, mocking implementations, and more in a single package.

Before Jest, the JavaScript ecosystem relied on several different tools and frameworks to give developers a way to write and run tests. Configuring these tools was rarely simple and easy. Jest aims to fix that by using sensible default configurations that work "out of the box", with little to no additional configuration required in most cases.

Jest is currently one of the most popular testing technology choices, consistently earning high satisfaction marks in the [State of JS developer survey since 2017](https://2020.stateofjs.com/en-US/technologies/testing/). It's the reliable choice for testing JavaScript projects.

> 💡**Note**
Jest also [supports](https://jestjs.io/docs/getting-started#using-typescript) TypeScript via Babel.

---

## How to install Jest?

Install the `jest` [package](https://jestjs.io/docs/getting-started) (and optional typings) to a new or existing project's `package.json` file using your package manager of choice:

```bash
# For NPM users
npm install --save-dev jest @types/jest

# Yarn users
yarn add --dev jest @types/jest
```

That's it! We're now ready to run tests with Jest.

> 💡**Note**
It's good practice to install Jest and any other testing tools as development dependencies. This speeds up installation in environments where only dependencies required for the project to build and run are installed.

---

## How to run tests with Jest?

To run tests with Jest call the `jest` [command](https://jestjs.io/docs/getting-started) inside the root of the project folder.

We will update the project's `package.json` with a test script that calls the `jest` command for us:

```jsx
{
	// ... package.json contents
	"scripts": {
		// ... existing scripts
		"test": "jest"
	}
}
```

We can now run the newly created `test` script:

```bash
# NPM users
npm run test

# Yarn users
yarn run test
```

If everything is set up correctly Jest will give us the results of any tests it found and ran.

> 💡**Note**
Jest exits with status code 1 when a test case fails. Seeing `npm ERR!` errors in the console is expected in this case.

---

## How to create a test with Jest?

To create a test for use with Jest we create a `*.spec.js` or `*.test.js` file that will contain our test cases.

> 💡**Note**
Jest is [configured by default](https://jestjs.io/docs/configuration#testmatch-arraystring) to look for `.js`, `.jsx`, `.ts` and `.tsx` files inside of `__tests__` folders, as well as any files with a suffix of `.test` or `.spec` (this includes files called `test` or `spec`).

Since `isInteger.js` is the name of the module we're testing, we will write our tests in an `isInteger.spec.js` file created in the same folder as the module:

```jsx
// isInteger.spec.js
test("Sanity check", () => {
	expect(true).toBe(true);
});
```

> 💡**Note**
Whether you choose to write tests inside a dedicated folder or right next to your modules, there is no right or wrong way to structure tests inside a project. Jest is flexible enough to work with most project architectures without configuration.

The test's description is "Sanity check". **Sanity checks** are basic tests to ensure the system behaves rationally. The test will assert that we expect the value `true` to be `true`.

Run the test and if it passes everything is set up correctly.

Congratulations! We just wrote our first test!

---

## How to write a test case in Jest?

To write a test case we first define the outcomes that we must validate to ensure that the system is working correctly.

The `isInteger.js` module is a function that takes one parameter and returns `true` if the parameter is an integer value or `false` if it isn't. We can create two test cases from that definition:

1. `isInteger()` passes for integer value;
2. `isInteger()` fails for non-integer value.

To create a test case in Jest we use the `test()` [function](https://jestjs.io/docs/api#testname-fn-timeout). It takes a test name string and handler function as the first two arguments.

> 💡**Note**
The `test()` function can also be called under the alias - `it()`. Choose one over the other depending on readability or personal preference.

Tests are based on assertions. **Assertions** are made up of [expectations](https://jestjs.io/docs/expect) and [matchers](https://jestjs.io/docs/expect#tobevalue). The simplest and most common assertion expects the tested value to match a specific value. 

An expectation is created with the `expect()` [function](https://jestjs.io/docs/expect#expectvalue). It returns an object of matcher methods with which we assert something expected about the tested value. The matcher [method](https://jestjs.io/docs/expect#tobevalue) `toBe()` checks if the expectation matches a given value.

In our tests, we can expect `isInteger()` to be `true` for the integer value 1, and `false` for the non-integer value 1.23.

```jsx
// isInteger.spec.js
const isInteger = require("./isInteger");

test("isInteger passes for integer value", () => {
	expect(isInteger(1)).toBe(true);
});

test("isInteger fails for non-integer value", () => {
	expect(isInteger(1.23)).toBe(false);
});
```

Running Jest should now give us a report on which tests pass, and which tests fail.

---

## How to use fixtures in Jest?

To use fixtures in Jest we can use the `test.each()` [function](https://jestjs.io/docs/api#testeachtablename-fn-timeout). It performs a test for each fixture in an array of fixtures. 

**Fixtures** are data representing conditions - such as function arguments and return values - under which the unit test is performed. Using fixtures is a quick and easy way to assert that a unit's behaviour matches expectations under different conditions without having to write multiple tests.

In Jest, a fixture can be a single value or an array of values. The fixture is available in the test handler function through parameters. The value or values of a fixture can be injected in the description through [printf formatting](https://jestjs.io/docs/api#describeeachtablename-fn-timeout).

```jsx
// isInteger.spec.js
const isInteger = require("./isInteger");

const integerNumbers = [-1, 0, 1];

test.each(integerNumbers)(
	"isInteger passes for integer value %j",
	(fixture) => expect(isInteger(fixture)).toBe(true)
);

// ... or...
const integerNumbers = [
  [-1, true],
  [-0, true],
  [1, true]
];

test.each(integerNumbers)(
	"isInteger passes for integer value %j with result %j",
	(fixture, result) => expect(isInteger(fixture)).toBe(result)
);
```

Running Jest should now give us a report on which tests pass, and which tests fail, where every test corresponds to a fixture from our array of fixtures.

> 💡**Note**
`%j` is a [printf formatting specifier](https://nodejs.org/api/util.html#util_util_format_format_args) that prints the value as JSON. It's a good choice for fixtures that contain values of different types.

---

## How to group test cases in Jest into a test suite?

To group test cases in Jest into a test suite we can use the `describe()` [function](https://jestjs.io/docs/api#describename-fn). It takes a suite name string and handler function as the first two arguments.

A **test suite** is a collection of test cases grouped together for execution purposes. The goal of a test suite is to organise tests by common behaviour or functionality. If all tests within a suite pass, we can assume that the tested behaviour or functionality meets expectations.

```jsx
// isInteger.spec.js
const isInteger = require("./isInteger");

describe("isInteger", () => {
	const integerNumbers = [-10, -1, 0, 1, 10];

	test.each(integerNumbers)(
		"passes for integer value %j",
		(fixture) => expect(isInteger(fixture)).toBe(true)
	);
	
	const floatNumbers = [-10.1, -1.1, 0.1, 1.1, 10.1];

	test.each(floatNumbers)(
		"fails for non-integer value %j",
		(fixture) => expect(isInteger(fixture)).toBe(false)
	);
});
```

Running Jest should now give us a report on which tests pass, and which tests fail, grouped into described test suites.

> 💡**Note**
`describe()` blocks can also be nested to create more complex test hierarchies.

---

## How to run Jest every time files change?

To run Jest every time files change we can use the `--watch` and `--watchAll` [flags](https://jestjs.io/docs/cli#--watch).

The `--watch` flag will tell Jest to watch for changes in files tracked by Git. Jest will run only those tests affected by the changed files. For this to work, the project must also be a Git repository.

The `--watchAll` flag will tell Jest to watch all files for changes. Whenever a file changes, Jest will run all tests.

Both `--watch` and `--watchAll` modes support additional filtering of tests while the tests are running. This makes it possible to only run tests matching a file name, or only run failing tests.

```bash
# Runs tests on changed files only and re-runs for any new change
# Note: the project must also be a git repository
jest --watch

# Runs tests on all files and re-runs for any new change
jest --watchAll
```

---

## How to get a test coverage report with Jest?

To get a test coverage report with Jest we can use the `--coverage` [flag](https://jestjs.io/docs/cli#--coverageboolean).

**Test coverage** is a software testing metric that describes how many lines of source code (statements) of the tested unit are executed (covered) by tests. A test coverage of 100% for a unit means every line of code in the unit has been called by the test.

We should always aim for a high test coverage - ideally 100% - but also keep in mind that total coverage does not mean we tested all cases, only lines of code.

```bash
# Runs tests and prints a test coverage afterwards
jest --coverage
```

> 💡**Note**
We can combine different flags to get more features out of Jest. For example, to watch all files and get a coverage report we can run `jest --watchAll --coverage`.

With that we're all set! We can now write tests and run them whenever a file changes, and also review test coverage reports for covered and uncovered lines of code.

---

## Jest unit test example code

To install Jest:

```bash
# For NPM users
npm install --save-dev jest @types/jest

# Yarn users
yarn add --dev jest @types/jest
```

The unit to be tested in `isInteger.js`:

```jsx
// isInteger.js
module.exports = (value) => !isNaN(parseInt(value, 10));
```

The unit test in `isInteger.spec.js`:

```jsx
// isInteger.spec.js
const isInteger = require("./isInteger");

describe("isInteger", () => {
	const integerNumbers = [-10, -1, 0, 1, 10];

	test.each(integerNumbers)(
		"passes for integer value %j",
		(fixture) => expect(isInteger(fixture)).toBe(true)
	);
	
	const floatNumbers = [-10.1, -1.1, 0.1, 1.1, 10.1];

	test.each(floatNumbers)(
		"fails for non-integer value %j",
		(fixture) => expect(isInteger(fixture)).toBe(false)
	);
});
```

The test script in `package.json`:

```bash
jest --watchAll --coverage
```

---

## Homework and next steps

- Write more comprehensive tests. How are strings handled? Objects? `null` and `undefined`? Consider adding more fixtures to cover these cases.
- Fix the code so the tests pass or write a newer, better implementation.
- Achieve 100% code coverage in the coverage report.

---

Thank you for taking the time to read through this article!

Have you tried writing unit tests in Jest before? How do you feel about Jest?

Leave a comment and start a discussion!