Megan Lee
Posted on April 18, 2024
Written by Ibiyemi Adewakun✏️
Jest is one of the most popular JavaScript testing libraries used for both unit and integration testing. It supports several JavaScript frameworks, including React, Angular, NestJS, and Express, making it a suitable testing option for both frontend and backend JavaScript projects.
In this article, we will dive deep into all things Jest to help you determine whether Jest is the right choice for your project. We’ll cover what is it, how it works, its features and limitations, and how it compares to other JavaScript testing libraries.
Introduction to Jest
The Jest testing framework was developed by Facebook (now Meta) with the goal of testing React components. It’s designed to assert the correctness of JavaScript code by utilizing a feature-rich and well-documented API.
Jest currently comes with support for Babel, TypeScript, Node, React, Angular, Vue, and several other JavaScript frameworks. It provides functionalities like mocking, assertion, code coverage, and exception context which help developers write tests on the behavior and outcome of the code.
Additionally, Jest provides support for mocking and spying on external dependencies, which makes it easy to test complex code.
The history and evolution of Jest
Jest was originally referred to as “jst” internally at Meta. The Meta team created Jest in 2011 when they rewrote their chat feature in JavaScript. It was open sourced in 2014 and maintained part-time by Meta engineers until 2016, when a dedicated team was formed to maintain it.
Jest’s community of open source contributors grew. In 2018, Meta founded the Jest Open Collective and raised over $70,000 in funds to support non-Meta employees contributing to, developing, and maintaining Jest.
Today, Jest is primarily maintained by a group of core, non-Meta-employee contributors to whom Meta has transferred ownership via the OpenJS Foundation.
How tests are executed in Jest
Jest is built of multiple, individually usable packages. These packages play different parts in executing a test with Jest via a command such as jest my-test.js
. Let’s take a look at how this happens.
When you run the jest my-test.js
command, the first package called is jest-cli
. This package executes the test, calling all other needed packages as necessary. When the test is initiated, jest my-test.js
calls the jest-config
package.
The Jest config takes the arguments in the test execution command and combines them with the config, typically defined in either the package.json
file or the jest.config.json
file. It then creates two config objects — the Global
config and the Project
config — and sends those back to the Jest CLI.
Jest then performs some static file analysis using the jest-haste-map
package. This package helps Jest find all relevant files in the project, understand dependencies between files, and know what files might be affected by a given code change.
This static file analysis step produces a HasteContext
along with a HasteFS
or the Haste File System, which is a list of all the files and their dependencies in the project. These are cached so that they only need to be recreated when there are file changes. Then, they’re reported back to the Jest CLI.
Now that all the preparatory work is done, Jest has a context
object defined. This object includes the HasteFS
, the Global
config, and the Project
config.
Let’s go back to the command that started it all:
jest my-test.js
Jest treats my-test.js
as a pattern, not as a path. So, the Jest CLI — utilizing its internal search function and the HasteFS
— finds all test files to run that match the my-test.js
pattern.
The Jest CLI then runs an additional step to order the tests, prioritizing tests that failed in the past to provide insights into a fault as early as possible. Next, it calls on the jest-runner
package to schedule the tests using the priority order generated in the previous step.
Then, the tests are executed according to the test schedule using the jest-circus
package. This package also provides some of Jest’s popular functions, like describe
and it
.
Each test is run in isolation using the jest-runtime
module, which runs each test in an isolated virtual machine (VM) context, and the jest-environment
module. At this point, mocking and the needed transformations are done for each test dependency resolution.
The results of the tests run are updated in the TestResults
object by jest-circus
. Then, they’re passed on to the reporters for display and aggregation.
Here’s a simplified overview of the Jest testing architecture and execution.
You can find a more detailed and complex view of Jest’s architecture and executions in this LinkedIn article.
Jest features you should know
In the upcoming sections, we’ll take a look at some of the standout features that make Jest a great tool.
Jest CLI tool
The Jest command line tool provides the jest
command for initiating tests, along with a variety of options you can enable when using Jest to run tests.
One of Jest’s biggest perks is how much customization it provides through its configuration — i.e., in the package.json
or jest.config.json
files. What’s even better is that all configuration options are available on the CLI tool.
Some of the most popular Jest CLI options are:
-
watch
: Tells Jest CLI to watch for file changes and re-run related tests to the edited files -
silent
: Prevents the tests from printing output messages to the command line while running -
verbose
: Enables detailed output on test runs and displays the test suite hierarchy -
findRelatedTests
: Takes a space separated list of source files as an argument and is used to run tests related the listed files. This helps the developer run the minimum number of tests needed to validate specific changes. It’s also commonly used along with thecoverage
option to provide the test coverage of the listed files
Code coverage
Jest provides a code coverage configuration, which helps highlight code paths that are not covered by tests. You can enable this feature by passing the --coverage
CLI option when running the Jest test command.
Supported frameworks and browsers
The Jest testing platform is able to adapt to any JavaScript library or framework. Jest supports many popular JavaScript frameworks and libraries with active communities. These include React, Vue.js, Angular, MobX, Redux, Express.js, GatsbyJS, Hapi.js, and Next.js.
Further reading:
- React app testing: Jest and React Testing Library
- Unit testing NestJS applications with Jest
- Testing Next.js apps with Jest
- Unit testing Vuex modules with Jest
- Node.js Express test-driven development with Jest
Sandboxes and isolation
Jest provides an option to sandbox and isolate modules which may have been mocked or spied in the execution of a test. This option is written as jest.isolateModules(fn)
and it takes a callback returning the modules to isolate. Here’s an example:
let myModule;
jest.isolateModules(() => {
myModule = require('myModule');
});
const otherCopyOfMyModule = require('myModule');
Mocking and spying support
Jest provides a mock function that allows us to replace the actual implementation of a function. Instead of implementing the function, we can use this mock function to keep track of calls and parameters passed into the calls, along with setting return values for the calls.
You can create a mock function can be created with the jest.fn()
command. Meanwhile, you can mock a module using the jest.mock(<module>)
command.
Snapshot testing
Snapshot testing is an option of Jest used to validate that some UI component is rendered as expected. The test renders a UI component, takes a snapshot and compares to a reference snapshot saved alongside the test. Snapshot assertions are executed using the format expect(rendered-component).toMatchSnapshot()
.
When executed the first time, Jest creates a snapshot file referenced for asserting future tests. This helps ensure that while making changes to an application, an unexpected change to a component does occur. Jest also provides an option to intentionally update the snapshot reference using the option --updateSnapshot
.
In addition to snapshot testing, Jest enables you to perform unit tests, integration tests, component tests, and visual regression tests.
Further reading:
- Using Jest for React screenshot testing
- Component testing in Nuxt.js with Jest
- Automated visual regression testing with Jest
Assertions API and matchers
The Jest Assertions API provides a variety of options to check that specific conditions are met. You can access these assertion options via the expect
API function.
Matchers, on the other hand, are assertion functions that compare a given value to some other expected value or condition. Some examples of popular matcher functions are:
-
toBe(value)
: Compares the value of some variable to what we expect its value to be. You could use it like so:expect(receivedVar).toBe(expectedValue)
-
toHaveBeenCalled()
: Used on a mock function to assert that the function was indeed invoked -
toHaveBeenCalledTimes(number)
: Also used on a mock function to assert the number of times the function was invoked -
toEqual(value)
: Used to assert that some variable is exactly equal to some expected value -
toThrow(error?)
: Used to assert that an exception was thrown in the execution of a function. You can also optionally pass an argument of either the error class, an instance of the error class, or a substring of the error message
Note that when we assert that a function execution throws an error, our function call must be wrapped in another function, like so:
expect(() => { … function call }).toThrow()
Jest provides many other matcher functions, which you can explore in more detail in the docs.
Hooks
Jest provides hooks as a mechanism to attach some function — for example, a callback to run some things — during certain events in the execution of a test suite.
The most common hook events are the beforeAll
and afterAll
hooks, which are popularly referred to as setup and teardown. These setup and teardown hooks are used to execute functions like seeding, setting mock responses, or clearing and resetting mocks while executing tests.
There are many other hooks available besides beforeAll
and afterAll
. For example, beforeEach
and afterEach
execute code in its callback before each test.
Blocks
Jest provides blocks as a way to group and improve the context of related test cases in a file. A block can contain both hooks and tests in an organized and contextual way.
Jest defines these blocks using the function describe
, which takes the following arguments: a string describing the block and a callback function executing the block body. Here’s an example:
// Block example
describe('block description', () => {
// hook within the block
beforeEach(() => {
// run some setup tasks
})
test('test case 1', () => {
// test description
})
})
Scoping
In Jest, scoping refers to the order of relevance and execution of the hooks and functions within a test file. Scoping defines which hooks are relevant to which test blocks and the order in which they are executed.
For example, the top-level beforeEach
and afterEach
hooks — i.e., those outside all blocks — are executed for every test in the file. Meanwhile, a beforeEach
hook within a describe
block is only run before each test within that block.
Interactive mode
Jest provides an interactive watch mode. You can enable this mode by adding the --watch
argument when running the Jest command.
With watch mode, Jest looks out for edits to the code files and reacts to these changes by re-running tests related to them.
The .only
and .skip
annotations
Jest provides an option to run a single test or block by using the .only
annotation, like so:
// Running a single test
test.only(fn)
// Running a single block of tests
describe.only(() => {
test(fn)
...
})
Also, as an alternative to running a single test or block of tests, Jest provides an option to skip certain tests or blocks using the .skip
annotation:
// Skipping a single test
test.skip(fn)
// Skipping a single block of tests
describe.skip(() => {
test(fn)
...
})
Further reading:
Why use Jest?
Now that we’ve explored Jest and some of its key features, let’s review the top reasons why you should use Jest:
- Configuration: Jest provides extensive and customizable configuration options for testing, with useful default options that minimizes the effort to setting up testing in a project
- Developer community: Jest is a widely used and supported testing framework. It’s maintained by a large open source community with a lot of resources for resolving issues, understanding its capabilities and best practices
- Mocking: Jest provides mocking capabilities out of the box that you can use to mock and spy on module and function dependencies. This helps reduce the complexity of testing code that uses external services
- Snapshot testing: Jest provides snapshot testing which generates component snapshots and compares them during test executions. This feature is particularly useful for testing frontend components that may have been accidentally changed, such as changes in child components that affect the content of the parent
- Code coverage: Jest generates code coverage reports that help identify code pathways that have not been covered by the available tests
- Interactive test watching: As mentioned above, Jest’s watch mode automatically detects and runs tests relevant to edits made to the code. Jest also has the ability to keep track of dependencies that might be affected by the edits and run tests targeted at those as well
Jest has many excellent features, comprehensive testing capabilities, and ease of use — along with wide support for many JavaScript frameworks and libraries. These make it a great choice for most JavaScript projects and many TypeScript projects as well.
Further reading:
Limitations of Jest
As we discuss when, how, and why to use Jest, it’s also important to consider its limitations. Here are a few you should keep in mind:
- IDE support: Jest is not supported on all IDEs. Users of Jest with unsupported IDEs will miss out on useful tooling like IntelliSense and automatic imports
- Speed: Jest runs a little slower than other test libraries like Mocha because of auto-mocking and loading dependencies first
- ES modules support: Jest does not fully support ECMAScript Modules (ES modules). Support is currently experimental because Jest was originally designed for CommonJS, making it a less obvious choice for implementing tests in JavaScript projects using ES modules
- Maturity: Jest does not have as much support and tooling as older test frameworks like Jasmine
Despite these limitations, Jest is still an excellent testing framework that is simple to use and even simpler to set up. It’s designed to work without custom configuration, and it provides a wide range of testing API functionality suitable for testing both backend and frontend JavaScript projects.
Use cases for Jest
Jest is a great unit and integration testing library option. Here are some specific testing use cases where Jest really shines:
- Full-stack JavaScript applications: Jest is compatible with and configurable to all JavaScript frameworks for both backend and frontend
- Testing complex applications: Jest is a great unit testing tool with its assertions API, test coverage, and mocking capabilities
- Testing large applications: Jest offers parallel testing which provides maximum performance and minimizes test run times by supporting parallel testing out of the box
- UI testing: Jest offers support for snapshotting, which helps assert the consistency of UI elements across multiple app versions
Beyond the ideas listed above, there are many other use cases for which Jest is a great choice. I encourage you to review its features and benefits to determine whether it works for your particular case.
Comparing Jest to similar test libraries
There are several other JavaScript testing libraries. In this section we will compare a few of them with Jest.
Jest vs. Mocha
Mocha, unlike Jest, doesn’t have inbuilt assertion or mocking capabilities. Instead, it relies on third-party tools like Sinon and Chai to provide these functionalities.
This reliance on plugins and external libraries makes Mocha flexible and extensible to choose your preferred assertion and mocking libraries. However, it also increases the complexity to get started and maintain its use in a project.
Mocha also provides additional functionalities, like:
- Highlighting slow running tests
- Being configurable to running tests directly in the web browser
- Retrying failed tests
Like with Jest, you can configure Mocha to work with multiple JavaScript frameworks — like React, Angular, Express, and more — covering both frontend and backend testing needs. Mocha also has full support for ES modules, while Jest’s support is limited and experimental.
Jest vs. Jasmine
Jasmine is an open source JavaScript testing framework. Similar to Jest, Jasmine has built-in assertion functionality and doesn’t require configuring external functionality for this.
Unlike Jest, everything needed to run Jasmine comes in a single package. Jasmine was developed in 2010 before Mocha and Jest, making it one of the oldest JavaScript testing frameworks.
Jasmine also supports most of the popular JavaScript frameworks, has highly readable tests, and has very good tool and community support.
Jest vs. the inbuilt Node.js test runner
The Node.js test runner module, node:test
, was released experimentally in version 18 and made stable in version 20. It provides some limited testing functionality to avoid Node developers being forced to rely on a third part library.
The Node test module provides a single function, within which single tests as call back functions or multiple subtests can be created. An example;
// node-test.test.js
const test = require('node:test')
// Single test
test('should pass', (t) => {
assert.strictEqual(1, 1);
});
// Multiple sub tests
test('has multiple tests', (t) => {
t.test('subtest 1', (t) => {
assert.strictEqual(1, 1);
});
t.test('subtest 2', (t) => {
assert.strictEqual(2, 2);
});
})
The Node.js test module similarly to Jest provides assertions, mocking, the skip/only
options to run or skip specific tests, watch mode, code coverage, test hooks and the describe/it
syntax for writing and grouping tests.
Comparison table: Jest vs. similar testing libraries
We’ve covered a lot of information in this section. Let’s summarize the information in the table below:
Features | Jest | Mocha | Jasmine | Node.js test module |
---|---|---|---|---|
Assertions | Built-in | External library i.e. Chai | Built-in | Built-in |
Mocks — stubs & spies | Built-in | External library i.e. Sinon | Built-in | Built-in |
Code coverage & reporting | Built-in | Built-in | Built-in | Limited |
Test hooks | Included | Included | Included | Included |
Asynchronous testing | Included | Included | Included | |
Parallel testing | Built-in | Not available | Available in v5.0.0 and later | Configurable |
Browser test | Not available | Not available | Available | Not available |
Snapshot testing | Available | Not available | Configurable via a third party library | Not available |
You can use this table to help you quickly assess each library’s features and determine whether Jest or another option might be best for your needs.
Further reading:
Integrating Jest with other testing libraries: Jest and Enzyme
Earlier, we compared Jest with several other test libraries. However, it’s useful to mention that we can also integrate Jest with other libraries to achieve more extensive testing.
A popular example is using Jest with Enzyme to test React UI components. Jest and Enzyme serve different purposes. Jest is a full testing framework providing test blocks, assertions, and several other features. Enzyme is a library that makes it easier to assert the correctness of React components by providing rendering functions.
When using Jest and Enzyme together, we can use Enzyme’s render function to render the component and then use Jest’s assertion function to validate some content. Here’s an example:
// Jest & Enzyme example
import MyParentComponent from './MyParentComponent';
import MyChildComponent from './MyChildComponent';
describe('<MyComponent />', () => {
it('renders three <MyChildComponent /> components', () => {
const wrapper = shallow(<MyParentComponent />);
expect(wrapper.find(MyChildComponent)).to.have.lengthOf(3);
});
});
Further reading:
Setting up Jest in a project
Now that we’re familiar with Jest and its capabilities and limitations, let’s look at how to add Jest to an existing project.
First, install Jest using your preferred package manager. In this article, I’ll be using Yarn:
$ yarn add --dev jest
Next, add the following to your package.json
file:
// package.json
{
"scripts": {
"test": "jest"
}
}
With these two simple steps, you’re ready to write some Jest tests in JavaScript code! However, Jest provides the option to make some more advanced configurations. We can create the Jest configuration file by running the command below:
$ yarn create jest@latest
You can find more Jest configuration options in the docs for Babel, webpack, and TypeScript.
Conclusion
Jest is feature-rich testing framework that comes with several in-built features like assertion, mocking, parallel test running, and more. It’s powerful, easy to use, and one of my favorite test frameworks.
Getting started with Jest is easy thanks to its useful defaults, but you can also configure it with more custom options. Jest also has a rich API, is well documented, and has a large and active community of contributors. This means there’s always lots of content available on its features and best practices.
I hope this guide was useful in helping you evaluate Jest as a testing tool for your next project. If you have any further questions about Jest, feel free to comment below.
Get set up with LogRocket's modern error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID.
- Install LogRocket via NPM or script tag.
LogRocket.init()
must be called client-side, not server-side.
NPM:
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Script Tag:
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
3.(Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
Posted on April 18, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
August 31, 2024