Software testing is vital for ensuring your applications work as expected, especially when you introduce changes. Catching and fixing errors early in development is crucial for maintaining resilient, high-quality code.
Of the many available tools and frameworks for JavaScript testing, Jest is one of the most popular. A product by Meta, Jest features extensive testing capabilities for JavaScript applications and those built with JavaScript frameworks.
Let’s explore the Jest framework, its features, and how best to integrate it into your development workflow.
What Is Jest?
Jest is a flexible framework and simple to use. In addition to its core JavaScript-testing features, it offers configurations and plugins to support testing Babel, webpack, Vite, Parcel, or TypeScript-based applications.
Jest has seen widespread adoption among developers and boasts an array of community-built and maintained plugins. It stands out for its ease of use: JavaScript testing requires no additional configurations or plugins. But you can also perform more advanced testing — like testing JavaScript frameworks — using some extra configuration options.
How To Set Up Jest for Your JavaScript Project
Let’s explore how to set up Jest in an existing JavaScript project.
Prerequisites
To follow this tutorial, ensure you have the following:
- Node.js installed.
- npm (already part of Node.js) or Yarn installed.
- The Jest npm package installed.
Install the Jest Package
- If you don’t already have a project to follow along with this tutorial, use this repo as a starting point.
The starter-files
branch gives you a base to build the application as you follow the tutorial. Reference the main
branch to view this tutorial’s code and cross-check your code.
- To install Jest with npm, go to the project directory in your terminal and run the following command:
npm install --save-dev jest
The --save-dev
option tells npm to install the package under devDependencies
, which contains the dependencies you need for development.
Configure Jest
Though Jest generally works without additional configuration, there are two ways to expand its power: in the package.json file and via a Jest configuration file.
Configure Jest in package.json
In your package.json file, add an object named jest
with properties as shown below:
{
…
"jest": {
"displayName": "Ecommerce",
"globals": {
"PROJECT_NAME": "Ecommerce TD"
},
"bail": 20,
"verbose": true
},
}
During the test, Jest searches this object and applies these configurations. You can view additional options on Jest’s configurations page, but this object’s properties include:
displayName
— Jest adds this property’s value as a label to your test results.globals
— Holds an object value to define global variables available in your test environments.bail
— By default, Jest runs through all tests and displays the errors in the results.bail
tells Jest to stop running after a set number of failures.verbose
— When set totrue
, this shows individual test reports during the test execution.
Configure Jest in a Configuration File
You can also configure Jest in a jest.config.js file. Jest also supports .ts, .mjs, .cjs, and .json extensions. When executing tests, Jest looks for these files and applies the settings in the file it finds.
For example, consider this jest.config.js file:
const config = {
displayName: "Ecommerce",
globals: {
"PROJECT_NAME": "Ecommerce TD"
},
bail: 20,
verbose: true
}
module.exports = config;
The code exports a Jest configuration object with the same properties as the previous example.
You can also use a custom file that contains a JSON-serializable configuration object and pass the file path to the --config
option when executing your tests.
Create a Basic Test File
With Jest configured, create your test files. Jest reviews your project’s test files, executes them, and provides the results. Test files usually follow a format such as [name].test.js or [name]-test.js. This pattern makes it easy for both Jest and your team to identify your test files.
Consider a string-format.js file that has the following code:
function truncate(
str,
count,
withEllipsis = true
) {
if (str.length < = count)
return str
const substring = str.substr(0, count)
if (!withEllipsis)
return substring
return substring + '...'
}
module.exports = { truncate }
The function truncate()
truncates strings to a particular length with the option to add an ellipsis.
Write the Test
- Create a test file named string-format.test.js.
- To keep your files organized, place string-format.test.js in the same directory you have the string-format.js file or in a specific test directory. Regardless of where your test file is within the project, Jest finds and executes it. With Jest, you can test your applications in various scenarios.
- Write a basic test in string-format.test.js as follows:
const { truncate } = require('./string-format')
test('truncates a string correctly', () = > {
expect(truncate("I am going home", 6)).toBe('I am g...')
})
The test case has the description truncates a string correctly
. This code uses the expect
function provided by Jest, which tests if a value matches the expected outcome.
The code passes truncate("I am going home", 6)
as an argument to expect
. This code tests the value returned from calling truncate
with the arguments "I am going home"
and 6
. The expect
call returns an expectation object, which offers access to Jest matches.
It also contains the toBe
matcher, which has "I am g…"
as an argument. The toBe
matcher tests equality between the expected and actual values.
Execute the Test
To execute your tests, define the jest
command.
- In your project’s package.json file, add this
test
script:
"scripts": {
"test": "jest"
}
- Now run
npm run test
,npm test
, ornpm t
in your terminal. It runs Jest for the project.
When you execute the tests, this is the result:
The results show one test suite (the string-format.test.js file), one test successfully executed ("truncates a string correctly"
), and the displayName
(Ecommerce
) that you defined in the configuration.
- In string-format.js, if you add an extra period to break the code and run the test, it fails:
This result suggests that you have broken the truncate
function or made updates that require updating the tests.
How To Write Tests With Jest
Jest Test Syntax
Jest’s proprietary syntax is simple to use. Jest exposes global methods and objects to your project for writing tests. Some of its fundamental terms are describe
, test
, expect
, and matchers.
describe
: This function groups related tests in a file.test
: This function runs the test. It’s an alias forit
. It contains assertions for the values you want to test.expect
: This function declares the assertions for various values. It provides access to matchers for various forms of assertions.- Matchers: They let you assert a value in various ways. You can assert value equality, boolean equality, and contextual equality (like whether an array contains the value).
To use them, consider the following example:
- Replace the test in the string-format.test.js file with the following code:
describe("all string formats work as expected", () = > {
test("truncates a string correctly", () = > {
expect(
truncate("I am going home", 6)
).toBe("I am g...")
})
})
- Run the code.
The result looks like the following:
The screenshot shows that the label in the describe
function creates a block. Although describe
is optional, grouping the tests in a file with more context is helpful.
Organize Tests in Test Suites
In Jest, a test case consists of the test
function, the expect
function and a matcher. A collection of related test cases is a test suite. In the earlier example, string-format.test.js is a test suite comprising one test case to test the string-format.js file.
Suppose you have more files in your project, like file-operations.js, api-logger.js, and number-format.js. You can create test suites for these files, like file-operations.test.js, api-logger.test.js, and number-format.test.js.
Write Simple Assertions with Jest Matchers
We’ve explored an example of using the toBe
matcher. Assertions with other Jest matchers include:
toEqual
— For testing “deep” equality in object instances.toBeTruthy
— For testing if a value is true in a boolean context.toBeFalsy
— For testing if a value is false in a boolean context.toContain
— For testing that an array contains a value.toThrow
— For testing that an invoked function throws an error.stringContaining
— For testing that a string contains a substring.
Let’s explore examples using some of these matchers.
You might, for example, expect a function or code to return an object with specific properties and values.
- Use the code snippet below to test this functionality. In this case, you want to assert that the returned object equals an expected object.
expect({
name: "Joe",
age: 40
}).toBe({
name: "Joe",
age: 40
})
This example uses toBe
. The test fails as this matcher doesn’t check for deep equality — it checks the value, not all properties.
- Use the
toEqual
matcher to check for deep equality:
expect({
name: "Joe",
age: 40
}).toEqual({
name: "Joe",
age: 40
})
This test passes as both objects are “deeply equal,” meaning all their properties are equal.
- Try another matcher example that tests if the defined array contains a specific element.
expect(["orange", "pear", "apple"]).toContain("mango")
This test fails because toContain
asserts that the ["orange", "pear", "apple"]
array contains an expected value "mango"
, but the array doesn’t.
- Use variables for the same test as with the code below:
const fruits = ["orange", "pear", "apple"];
const expectedFruit = "mango";
expect(fruits).toContain(expectedFruit)
Test Asynchronous Code
So far, we’ve tested synchronous code — expressions that return a value before the code executes the following line. You can also use Jest for asynchronous code with async
, await
, or Promises.
For example, the apis.js file has a function for making an API request:
function getTodos() {
return fetch('https://jsonplaceholder.typicode.com/todos/1')
}
The getTodos
function sends a GET
request to https://jsonplaceholder.typicode.com/todos/1
.
- Create a file named apis.test.js with the following code to test the fake API:
const { getTodos } = require('./apis')
test("gets a todo object with the right properties", () = > {
return getTodos()
.then((response) = > {
return response.json()
})
.then((data) = > {
expect(data).toHaveProperty('userId')
expect(data).toHaveProperty('id')
expect(data).toHaveProperty('title')
expect(data).toHaveProperty('completed')
expect(data).toHaveProperty('description')
})
})
This test case invokes the getTodos
function that fetches a todo
object. When it resolves the Promise, it uses the .then
method to get the resolved value.
In that value, the code returns response.json()
, which is another Promise that converts the response to JSON format. Another .then
method gets the JSON object containing the expect
and matchers. The code asserts that the JSON object includes five properties: userId
, id
, title
, completed
, and description
.
- Execute the tests:
As the screenshot shows, the test for getTodos()
fails. It expects the description
property, but the API doesn’t return it. With this information, you can now ask your company’s API management team to include that property if the application needs it or update the tests to meet the API’s response.
- Remove the assertion for the
description
property and rerun the tests:
The screenshot shows that everything passed the test.
- Now try using
async/await
instead of traditional Promise handling:
test("gets a todo object with the right properties", async () = > {
const response = await getTodos()
const data = await response.json()
expect(data).toHaveProperty("userId")
expect(data).toHaveProperty("id")
expect(data).toHaveProperty("title")
expect(data).toHaveProperty("completed")
})
The async
keyword is now before the function. The code uses await
before getTodos()
and await
before response.json()
.
Advanced Jest Features
Mock Functions and Modules
You may want to test an expression with external dependencies when writing tests. In some cases, especially unit testing, your unit tests should be isolated from external effects. In that case, you can mock your functions or modules with Jest to better control your tests.
- For example, consider a functions.js file that contains the following code:
function multipleCalls(count, callback) {
if (count < 0) return;
for (let counter = 1; counter <= count; counter++) {
callback()
}
}
The multipleCalls
function is executed based on the value of count
. It depends on the callback function — the external dependency. Its purpose is to know whether multipleCalls
executes the external dependency correctly.
- To mock the external dependency and track the dependency’s state in your test file, functions.test.js, use this code:
const { multipleCalls } = require('./functions')
test("functions are called multiple times correctly", () => {
const mockFunction = jest.fn()
multipleCalls(5, mockFunction)
expect(
mockFunction.mock.calls.length
).toBe(5)
})
Here, the fn
method of the jest
object creates a mock function. Then, the code executes multipleCalls
by passing 5
and the mock function as arguments. Then, it asserts that the mockFunction
is called five times. The mock
property contains information about how the code calls the function and returned values.
- When you run the test, this is the expected result:
As demonstrated, the code calls the mockFunction
five times.
In the code, the mock function imitates an external dependency. It doesn’t matter what the external dependency is when the application uses multipleCalls
in production. Your unit test doesn’t care how the external dependency works. It just verifies that multipleCalls
works as expected.
- To mock modules, use the
mock
method and pass a file path, which is the module:
const {
truncate,
} = require("./string-format")
jest.mock("./string-format.js")
This code imitates all functions that string-format.js exports and tracks how often it calls them. The module’s truncate
becomes a mock function, which causes the function to lose its original logic. You can find out how many times truncate
executes in your tests in the truncate.mock.calls.length
property.
If you have an error or your code doesn’t work, compare your code with the complete implementation.
Test React Components With Jest and React Testing Library
If you don’t already have a project to follow along with this tutorial, you can use this React example project as a starting point. The starter-files
branch helps you start composing the code as you follow the tutorial. Use the main
branch as a reference to cross-check your code against this tutorial’s complete code.
You can use Jest to test JavaScript frameworks such as React. When you create React projects using Create React App, they support React Testing Library and Jest out of the box. If you create a React project without Create React App, install Jest to test React with Babel and the React testing library. If you clone the starter-app
branch, you don’t need to install dependencies or apply configurations.
- If you are using the example project, use this command to install the required dependencies:
npm install --save-dev babel-jest @babel/preset-env @babel/preset-react react-testing-library
You can also use Enzyme instead of React Testing Library.
- Update your Babel configurations in babel.config.js or create this file if it doesn’t exist:
module.exports = {
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};
- Consider the src/SubmitButton.js file that has the following code:
import React, { useState } from 'react'
export default function SubmitButton(props) {
const {id, label, onSubmit} = props
const [isLoading, setisLoading] = useState(false)
const submit = () => {
setisLoading(true)
onSubmit()
}
return
This SubmitButton
component receives three props:
id
— The button’s identifier.label
— What text to render in the button.onSubmit
— What function to trigger when someone clicks the button.
The code assigns the id
prop to the data-testid
attribute, which identifies an element for testing.
The component also tracks the isLoading
state and updates it to true
when someone clicks the button.
- Create the test for this component. Place the following code in a SubmitButton.test.js file:
import {fireEvent, render, screen} from "@testing-library/react"
import "@testing-library/jest-dom"
import SubmitButton from "./SubmitButton"
test("SubmitButton becomes disabled after click", () => {
const submitMock = jest.fn()
render(
<SubmitButton
id="submit-details"
label="Submit"
onSubmit={submitMock}
/ >
)
expect(screen.getByTestId("submit-details")).not.toBeDisabled()
fireEvent.submit(screen.getByTestId("submit-details"))
expect(screen.getByTestId("submit-details")).toBeDisabled()
})
The code above renders the SubmitButton
component and uses the screen.getByTestId
query method to get the DOM node by the data-testid
attribute.
The first expect
is getByTestId("submit-details")
and uses the not
modifier and toBeDisabled
matcher (exposed from react-testing-library
) to assert that the button isn’t disabled. Use the not
modifier with every matcher to assert the matcher’s opposite.
Then, the code fires the submit
event on the component and checks that the button is disabled. You can find more custom matchers in the testing library documentation.
- Now, run the tests. If you cloned the
starter-files
branch, ensure you have all the project dependencies installed by runningnpm install
before starting your tests.
Run Code Coverage Reports
Jest also offers code coverage reports to show how much of your project you’re testing.
- Pass the
--coverage
option to Jest. In your Jest script in package.json (in the JavaScript project), update the Jest command with this coverage option:
"scripts": {
"test": "jest --coverage"
}
- Run
npm run test
to test your code. You get a report like the following:
This report shows that Jest tested 100% of the functions in SubmitButton.js and string-format.js. It also indicates that Jest hasn’t tested any statements and lines in string-format.js. The test coverage shows that the uncovered lines in string-format.js are 7 and 12.
On line 7, return str
in the truncate
function doesn’t execute because the condition if (str.length <= count)
returns false
.
On line 12, also in the truncate
function, the return substring
doesn’t execute because the condition if (!withEllipsis)
returns false.
Integrate Jest With Your Development Workflow
Let’s see how you can integrate these tests to improve your development workflow.
Run Tests in Watch Mode
Instead of manually executing tests, you can run them automatically when you change your code using watch mode.
- To enable watch mode, update your Jest command script in package.json (in the JavaScript project) by adding the
--watchAll
option:
"scripts": {
"test": "jest --coverage --watchAll"
}
- Run
npm run test
. It triggers Jest in watch mode:
The tests run every time you change your project. This approach promotes continuous feedback as you build your application.
Set Up Pre-Commit Hooks
In Git environments, hooks execute scripts whenever a particular event happens (such as pull, push, or commit). Pre-commit hooks define which scripts run for the pre-commit event (which the code triggers before making a commit).
The commit only succeeds if the script doesn’t throw an error.
Running Jest before pre-commit ensures that none of your tests fail before committing.
You can use various libraries to set up git hooks in your project, such as ghooks.
- Install
ghooks
underdevDependencies
:
npm install ghooks --save-dev
- Add a
configs
object in the top level of your package.json file (in the JavaScript project). - Add a
ghooks
object underconfigs
.
- Add a property with a key of
pre-commit
and a value ofjest
.
{
…
"config": {
"ghooks": {
"pre-commit": "jest"
}
},
}
- Commit the code. The code triggers the pre-commit hook, which executes Jest:
Summary
Now you know how to integrate Jest into your development workflow to automatically execute whenever you make a change. This approach provides continuous feedback so you can quickly fix any code issues before releasing your changes to production.
By hosting your application with Kinsta, you benefit from a fast and secure infrastructure, deploying your projects on infrastructure built on Google Cloud Platform’s Premium Tier network and C2 machines. Choose between 25 data centers and an HTTP/3-enabled CDN.
Stay secure with isolated container technology, two strong firewalls, and advanced Cloudflare-powered DDoS protection. And you can integrate apps or automate workflows with the Kinsta API.
Set up Jest and browse Kinsta’s resources today to improve your JavaScript applications.
Leave a Reply