Jest is a widely used JavaScript testing framework developed to provide a fast, reliable, and feature-rich testing experience.
With Jest, you get out-of-the-box support for test runners, assertions, snapshots, and mocking, making it a good choice for testing Node.js backends, React applications, and anything in between.
Its zero-config setup allows you to start writing tests immediately without additional configuration.
In this section, you'll explore Jest’s core features and learn how to write and run tests effectively.
Prerequisites
Before diving into Jest, ensure you have Node.js installed, preferably the latest LTS version.
This guide also assumes a basic understanding of JavaScript and testing principles.
Step 1 — Setting up the directory
Before you start writing tests, you will set up a proper environment for Jest. This will ensure your project is structured correctly and Jest runs smoothly with modern JavaScript features.
To begin, create a new directory and navigate into it:
mkdir jest-demo && cd jest-demo
Then, initialize the directory as an npm project:
npm init -y
This command creates a package.json
file for your project configuration. Now, enable ES Modules support:
npm pkg set type="module"
Then, install Jest as a development dependency:
npm install --save-dev jest
After installing Jest, update your package.json
file to include a test script:
{
. . .
"scripts": {
"test": "node --experimental-vm-modules node_modules/.bin/jest"
}
}
This ensures Jest runs in ESM mode since Jest's ESM support is still experimental.
Next, create a simple function to test. Save the following file as math.js
:
export function add(a, b) {
return a + b;
}
The add()
function takes two numbers and returns their sum.
With the function in place, you're now ready to write your first Jest test.
Step 2 — Writing your first test
Now that you have your add()
function, it’s time to write a test to verify that it works as expected. Instead of manually running the function and checking results yourself, you’ll use Jest to automate this process.
First, create a directory to organize your tests. Jest commonly uses a __tests__
folder by convention:
mkdir __tests__
Next, create a test file for your math module. Jest recognizes files with .test.js
or .spec.js
extensions:
import { add } from "../math.js";
describe("add function", () => {
it("should return 3 when adding 1 and 2", () => {
expect(add(1, 2)).toBe(3);
});
});
In this test, you import only the add
function—Jest automatically provides describe
, it
, and expect
globally.
The describe
block groups related tests for better organization, while each it
function defines a specific test case with a clear description.
The expect
function with the toBe
matcher verifies that add(1, 2)
equals 3
.
Now that you have written the test, you’re ready to run it in the next step.
Step 3 — Running your tests
With your test file in place, you can now run Jest to verify that everything works as expected.
To execute all test files in your project, run the following command:
npm test
Since the test script in package.json
is set to jest
, this command is equivalent to:
npx jest
Jest will automatically detect and execute test files inside the tests
directory. If everything is working correctly, you should see output similar to this:
> jest-demo@1.0.0 test
> node --experimental-vm-modules node_modules/.bin/jest
(node:13337) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS __tests__/math.test.js
add function
✓ should return 3 when adding 1 and 2 (1 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.148 s, estimated 1 s
Ran all test suites.
Jest successfully detected and executed the math.test.js
file inside the __tests__
directory. The output confirms that the test passed, displaying relevant details such as the number of test suites and tests executed, their pass status, and the total execution time.
Additionally, a warning about the experimental VM Modules feature is shown, along with an estimate of the expected runtime.
By default, Jest runs tests once and exits. However, you can enable watch mode for continuous testing:
npm test -- --watchAll
In watch mode, Jest monitors your project files and automatically reruns tests when changes are detected. This creates a rapid feedback loop that helps you identify and fix issues immediately while developing.
Step 4 — Test filtering and running specific tests
Running the entire test suite for every small change becomes inefficient as your test suite grows. Jest provides several ways to run only relevant tests, helping you maintain a fast development workflow.
Using .only
to focus on specific tests
When you need to concentrate on a particular test or test group, Jest's .only
modifier runs just the tests you specify, temporarily ignoring others.
For example, to focus only on the first test in your math module:
describe("add function", () => {
it.only("should return 3 when adding 1 and 2", () => {
expect(add(1, 2)).toBe(3);
});
it("should return 5 when adding 2 and 3", () => {
expect(add(2, 3)).toBe(5);
});
});
When you run the tests, Jest will execute only the test with .only
:
(node:13980) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS __tests__/math.test.js
add function
✓ should return 3 when adding 1 and 2 (2 ms)
○ skipped should return 5 when adding 2 and 3
Test Suites: 1 passed, 1 total
Tests: 1 skipped, 1 passed, 2 total
Snapshots: 0 total
Time: 0.387 s, estimated 1 s
Ran all test suites.
...
Notice that Jest marks the second test as skipped, letting you focus solely on the functionality you're developing or debugging.
You can also use describe.only
to run all tests within a specific test suite:
describe.only("add function", () => {
...
});
describe("subtract function", () => {
// All tests in this suite will be skipped
...
});
Using .skip
to temporarily exclude tests
When a test is temporarily broken, or you want to exclude specific tests, Jest's .skip
modifier allows you to skip them without removing or commenting out the code:
describe("add function", () => {
it.skip("should return 3 when adding 1 and 2", () => {
expect(add(1, 2)).toBe(3);
});
it("should return 5 when adding 2 and 3", () => {
expect(add(2, 3)).toBe(5);
});
});
PASS __tests__/math.test.js
add function
✓ should return 5 when adding 2 and 3 (4 ms)
○ skipped should return 3 when adding 1 and 2
Test Suites: 1 passed, 1 total
Tests: 1 skipped, 1 passed, 2 total
Snapshots: 0 total
Time: 0.478 s, estimated 1 s
Ran all test suites.
Watch Usage: Press w to show more.
The output confirms one skipped test, one passed test, and successful execution of all test suites.
Similarly, you can skip an entire suite with describe.skip
:
describe("add function", () => {
...
});
describe.skip("subtract function", () => {
// All tests in this suite will be skipped
...
});
This approach is beneficial during refactoring when specific tests might temporarily fail, but you still want to maintain your test coverage.
Filtering tests with command line arguments
Jest offers powerful command-line options to filter tests without modifying your code—ideal for CI/CD pipelines or selective testing.
Let's restore the test suite without any filtering:
import { add } from "../math.js";
describe("add function", () => {
it("should return 3 when adding 1 and 2", () => {
expect(add(1, 2)).toBe(3);
});
it("should return 5 when adding 2 and 3", () => {
expect(add(2, 3)).toBe(5);
});
});
To run only tests containing specific text in their description, use the -t
or --testNamePattern
flag:
npm test -- -t "add function"
The double dash --
passes the subsequent arguments to Jest rather than npm. This command runs all tests with "add function" in their description.
You can further narrow your selection with more specific patterns:
npm test -- -t "should return 3"
PASS __tests__/math.test.js
add function
✓ should return 3 when adding 1 and 2 (1 ms)
○ skipped should return 5 when adding 2 and 3
Test Suites: 1 passed, 1 total
Tests: 1 skipped, 1 passed, 2 total
Snapshots: 0 total
Time: 0.147 s, estimated 1 s
Ran all test suites with tests matching "should return 3".
You can also filter by file path to run only tests in specific files:
npm test -- math
This runs all test files with "math" in their path. For more precise control, specify the full path:
npm test -- __tests__/math.test.js
Interactive watch mode filtering
Jest's watch mode provides a convenient interactive interface for filtering tests:
npm test -- --watchAll
In watch mode, you can access different filtering options by pressing:
p
to filter by filename patternt
to filter by test name patternf
to run only failed testso
to run only tests related to changed files
This menu-driven approach makes it easy to quickly focus on relevant tests during development without remembering specific command-line arguments.
Watch Usage
› Press f to run only failed tests.
› Press o to only run tests related to changed files.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press q to quit watch mode.
› Press Enter to trigger a test run.
With these filtering capabilities, Jest lets you maintain focus on the specific parts of the codebase you're working on, significantly speeding up the development feedback loop.
Step 5 — Mocking with Jest
Mocking allows you to replace actual implementations with simulated ones, making it possible to test your code in isolation from external dependencies. This approach is useful when testing functions interacting with file systems, databases, or APIs.
In this section, you'll learn how to use Jest's mocking capabilities to test a function that reads from a file without accessing the file system.
First, create an empty text file:
touch text-content.txt
Next, create a fileReader.js
file with a function that reads a file's contents:
import { readFile } from "fs/promises";
export async function readFileContent(filePath) {
try {
const content = await readFile(filePath, "utf-8");
return content.trim();
} catch (error) {
throw new Error(`Failed to read file: ${error.message}`);
}
}
This function uses the Promise-based fs.readFile
to asynchronously read a file's contents and return them as a trimmed string.
Now, create a test file for this function. When working with ES Modules, Jest requires a special approach for mocking:
import { jest } from "@jest/globals";
jest.unstable_mockModule("fs/promises", () => ({
readFile: jest.fn(),
}));
const { readFileContent } = await import("../fileReader.js");
const { readFile } = await import("fs/promises");
describe("readFileContent function", () => {
it("should read and return the content from a file", async () => {
// Mock the implementation of readFile to return a specific value
readFile.mockResolvedValue("Mocked file content");
// Call the function with a file path
const content = await readFileContent("text-content.txt");
// Verify the mock was called with the correct arguments
expect(readFile).toHaveBeenCalledTimes(1);
expect(readFile).toHaveBeenCalledWith("text-content.txt", "utf-8");
// Check that the function returns the mocked content
expect(content).toBe("Mocked file content");
});
});
Unlike CommonJS, ES Modules require a different mocking approach.
Here, jest.unstable_mockModule()
replaces the real fs/promises
module with a mock version before importing fileReader.js
. This ensures that when readFileContent()
is executed, it relies on the mocked readFile
function instead of making actual file system calls.
The mock implementation of readFile
returns a Promise that resolves to "Mocked file content"
, allowing the test to control the function’s behavior without accessing the file system.
This approach makes the test:
- Eliminates file I/O operations.
- Avoids issues related to file existence or permissions.
- Always returns the expected output.
Running the test should produce output similar to:
(node:15938) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS __tests__/math.test.js
(node:15937) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS __tests__/fileReader.test.js
Test Suites: 2 passed, 2 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 0.512 s, estimated 1 s
Ran all test suites.
Watch Usage: Press w to show more.
Jest provides several helpful assertion methods specifically for mocks:
toHaveBeenCalled()
- verifies that a mock function was calledtoHaveBeenCalledWith()
- checks that a mock was called with specific argumentstoHaveBeenCalledTimes()
- ensures a mock was called an exact number of times
These matchers make your tests more readable and provide better error messages when tests fail.
Step 6 — Using setup and teardown hooks
When writing multiple tests, you'll often need to perform the same setup or cleanup tasks before or after each test. Jest provides a set of powerful hooks to handle these repetitive operations, helping you maintain clean and efficient test suites.
Let's build on our file reader example and incorporate setup and teardown functionality using Jest's built-in hooks. These hooks will ensure our mocks are properly reset and maintained across multiple tests.
Update the fileReader.test.js
file to include these hooks:
import { jest } from "@jest/globals";
jest.unstable_mockModule("fs/promises", () => ({
readFile: jest.fn()
}));
// Import the modules after mocking
const { readFileContent } = await import("../fileReader.js");
const { readFile } = await import("fs/promises");
describe("readFileContent function", () => {
beforeEach(() => {
// Reset mock before each test
jest.clearAllMocks();
// Set up default mock behavior
readFile.mockResolvedValue("Mocked file content");
});
it("should read and return the content from a file", async () => {
// Remove the readFile.mockResolvedValue("Mocked file content"); line
// Call the function with a file path
const content = await readFileContent("text-content.txt");
// Verify the mock was called with the correct arguments
expect(readFile).toHaveBeenCalledTimes(1);
expect(readFile).toHaveBeenCalledWith("text-content.txt", "utf-8");
// Check that the function returns the mocked content
expect(content).toBe("Mocked file content");
});
});
The beforeEach
hook executes before each test in the describe block. Here, it serves two purposes: resetting all mocks to their initial state with jest.clearAllMocks()
and configuring the default behavior for our readFile
mock. This ensures each test starts with a clean slate, preventing any cross-test interference.
When you run this test, you should see output similar to:
(Use `node --trace-warnings ...` to show where the warning was created)
PASS __tests__/math.test.js
(node:17590) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS __tests__/fileReader.test.js
Test Suites: 2 passed, 2 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 0.372 s, estimated 1 s
Ran all test suites.
Jest offers four main setups and teardown hooks, each with specific use cases:
beforeEach
: Runs before each test, ideal for resetting state or creating fresh test dataafterEach
: Runs after each test, perfect for cleanup operationsbeforeAll
: Runs once before all tests in a describe block, good for expensive setup operationsafterAll
: Runs once after all tests in a describe block, useful for final cleanup
For example, when working with database connections or other expensive resources, you might prefer beforeAll
and afterAll
to avoid repeated setup costs:
describe("Database operations", () => {
beforeAll(async () => {
// Connect to database once for all tests
await db.connect();
});
afterAll(async () => {
// Disconnect after all tests complete
await db.disconnect();
});
// Your tests here
});
These setups and teardown hooks will create more organized tests that properly isolate each test case while avoiding repetitive code.
This approach leads to a more maintainable test suite and helps ensure your tests accurately represent how your code should behave in real-world scenarios.
Step 7 — Code coverage with Jest
Code coverage helps you understand how thoroughly your code is being tested. Jest includes built-in coverage reporting capabilities that make identifying untested areas of your code and improving your testing strategy easy.
To enable coverage reporting in Jest, you only need to add the --coverage
flag when running your tests.
Update the package.json
file to include a dedicated script for running tests with coverage:
{
. . .
"scripts": {
"test": "node --experimental-vm-modules node_modules/.bin/jest",
"test:coverage": "node --experimental-vm-modules node_modules/.bin/jest --coverage"
}
}
Now, you can generate a coverage report by running:
npm run test:coverage
This will execute your tests and produce a detailed coverage report. The output will look similar to this:
(Use `node --trace-warnings ...` to show where the warning was created)
PASS __tests__/fileReader.test.js
PASS __tests__/math.test.js
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 80 | 100 | 100 | 80 |
fileReader.js | 75 | 100 | 100 | 75 | 8
math.js | 100 | 100 | 100 | 100 |
---------------|---------|----------|---------|---------|-------------------
Test Suites: 2 passed, 2 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 0.308 s, estimated 1 s
Ran all test suites.
Running your tests will generate a detailed coverage report that provides insights into how well your code is tested. The report includes several key metrics:
- Statement coverage – Measures the percentage of executed statements.
- Branch coverage – Tracks how many control structures, such as
if
statements, were tested. - Function coverage – Indicates the percentage of functions that were called.
- Line coverage – Reflects the proportion of executed executable lines.
In this report, math.js
achieves 100% coverage across all metrics. This means every statement, branch, function, and line in the file is tested. However, fileReader.js
has only 75% line coverage, indicating that some code paths remain untested. Specifically, the tests do not cover lines 8-9, which handle error scenarios. Addressing these gaps can help improve overall test coverage and ensure robust error handling in your code.
Let's improve the coverage by adding a test for the error case in fileReader.js
:
import { jest } from "@jest/globals";
// Mock the fs/promises module
jest.unstable_mockModule("fs/promises", () => ({
readFile: jest.fn()
}));
// Import the modules after mocking
const { readFileContent } = await import("../fileReader.js");
const { readFile } = await import("fs/promises");
describe("readFileContent function", () => {
beforeEach(() => {
jest.clearAllMocks();
// remove the mock implementation for this test
});
it("should read and return the content from a file", async () => {
// Configure the mock implementation for this test
readFile.mockResolvedValue("Mocked file content");
// Call the function with a file path
const content = await readFileContent("text-content.txt");
// Verify the mock was called with the correct arguments
expect(readFile).toHaveBeenCalledTimes(1);
expect(readFile).toHaveBeenCalledWith("text-content.txt", "utf-8");
// Check that the function returns the mocked content
expect(content).toBe("Mocked file content");
});
it("should throw an error when file reading fails", async () => {
// Configure the mock to reject with an error
readFile.mockRejectedValue(new Error("File not found"));
// Verify that the function throws an error with the expected message
await expect(readFileContent("non-existent.txt")).rejects.toThrow(
"Failed to read file: File not found"
);
// Ensure the mock was called with the correct arguments
expect(readFile).toHaveBeenCalledTimes(1);
expect(readFile).toHaveBeenCalledWith("non-existent.txt", "utf-8");
});
});
The new changes modify the beforeEach
hook by removing the default mock implementation of readFile.mockResolvedValue("Mocked file content")
, ensuring that each test explicitly defines its own mock behavior.
The existing test is also updated to explicitly configure the mock for a successful file read, making each test more independent and preventing unintended mock behaviors from carrying over.
Additionally, a new test case is added to verify that the function correctly throws an error when file reading fails.
Running the coverage report again reflects these improvements, with all metrics reaching 100%:
The percentage values in the report are highlighted in green, indicating that every statement, branch, function, and line of code is fully tested.
Jest's coverage reporting also generates a detailed HTML report you can view in your browser. By default, this report is saved in the coverage
directory at the root of your project:
Open coverage/lcov-report/index.html
in your browser to see a more interactive version of the coverage report.
With coverage reports in place, you can adopt a coverage-driven testing approach to help ensure your codebase is thoroughly tested and more resilient to changes.
Final thoughts
In this guide, you’ve explored Jest's core features and learned how to write, run, and optimize tests effectively.
You started by setting up a Jest testing environment, writing and running your first test, and then explored more advanced topics like test filtering, mocking, setup and teardown hooks, and code coverage.
To further enhance your testing skills, dive into Jest’s official documentation, where you'll find more in-depth information to take your testing workflow to the next level.
Happy testing!
Make your mark
Join the writer's program
Are you a developer and love writing and sharing your knowledge with the world? Join our guest writing program and get paid for writing amazing technical guides. We'll get them to the right readers that will appreciate them.
Write for us
Build on top of Better Stack
Write a script, app or project on top of Better Stack and share it with the world. Make a public repository and share it with us at our email.
community@betterstack.comor submit a pull request and help us build better products for everyone.
See the full list of amazing projects on github