Testing JavaScript with Jest
Jest is a library that allows you to test JavaScript code, and it is widely used for testing React applications. It is fast, easy to use, and maintained by Facebook. In this article, we will explore the different features of Jest and how to write effective tests using it.
Introduction to Jest
Jest is an open-source project maintained by Facebook. It is designed for testing JavaScript code and is particularly well-suited for testing React applications. However, it can be used to test any JavaScript code. Some of the strengths of Jest include:
- It is fast
- It supports snapshot testing
- It provides an all-in-one solution without requiring additional testing libraries
Compared to other testing frameworks like Mocha, Jest is more opinionated and provides a set of conventions out of the box. This makes it easier to set up and start writing tests without having to make many configuration choices. Additionally, Jest has excellent tooling integrations and is widely adopted within the JavaScript community.
Installation
If you are using create-react-app
, Jest is automatically installed and configured for you, so you don’t need to install it separately. Otherwise, you can install Jest using either Yarn or npm:
1 | yarn add --dev jest |
or
1 | npm install --save-dev jest |
Make sure to add the following line to the scripts section of your package.json
file:
1 | { |
This allows you to run your tests using the command yarn test
or npm run test
. Alternatively, you can install Jest globally by executing the following command:
1 | yarn global add jest |
This allows you to run all your tests using the jest
command in the terminal.
Creating the first Jest test
If you are using create-react-app
, Jest is already set up with a default test file. However, if you need to add Jest to an existing project, you can do so by following these steps:
- Create a file called
math.js
and define some functions that you want to test. For example:
1 | const sum = (a, b) => a + b; |
- Create a test file called
math.test.js
in the same folder. In this file, you can use Jest to test the functions defined inmath.js
:
1 | const { sum, mul, sub, div } = require('./math'); |
To run your tests, execute the command yarn test
or npm run test
in your terminal. Jest will run all the test files it finds and display the results.
Running Jest with VS Code
If you are using Visual Studio Code as your editor, you can enhance your testing experience by installing the Jest extension (available in the marketplace). Once installed, the extension automatically detects if Jest is installed in your project and runs the tests. You can also manually invoke the tests by selecting the “Jest: Start Runner” command. This allows you to run the tests and stay in watch mode, which means the tests will be re-run whenever you make changes to the test files or the code being tested.
Matchers
Jest provides a variety of matchers that allow you to test values in different ways. Some commonly used matchers include:
toBe()
: compares the strict equality of the expected value and the actual valuetoEqual()
: compares the values of two variables or objectstoBeNull()
: checks if the value is nulltoBeDefined()
: checks if the value is definedtoBeUndefined()
: checks if the value is undefinedtoBeCloseTo()
: compares floating-point numbers with a certain precisiontoBeTruthy()
: checks if the value is truthytoBeFalsy()
: checks if the value is falsytoBeGreaterThan()
: checks if the actual value is greater than the expected valuetoBeGreaterThanOrEqual()
: checks if the actual value is greater than or equal to the expected valuetoBeLessThan()
: checks if the actual value is less than the expected valuetoBeLessThanOrEqual()
: checks if the actual value is less than or equal to the expected valuetoMatch()
: checks if a string matches a regular expression patterntoContain()
: checks if an array or string contains the expected valuetoHaveLength()
: checks the length of an array or stringtoHaveProperty()
: checks if an object has a specific property and valuetoThrow()
: checks if a function throws an exceptiontoBeInstanceOf()
: checks if an object is an instance of a specific class
All of these matchers can be negated using the .not
modifier. For example, you can use expect().not.toEqual()
to check for inequality.
Setup and Teardown
Before running your tests, you may need to perform some setup or cleanup tasks. Jest provides hooks for setting up and tearing down test environments.
You can use the beforeAll()
and afterAll()
functions to initialize or clean up the test environment once. These functions run only once, before or after all the tests:
1 | beforeAll(() => { |
If you need to perform a setup or cleanup task before or after each test, you can use the beforeEach()
and afterEach()
functions:
1 | beforeEach(() => { |
These functions can be useful for tasks like creating temporary files or initializing a test database. They ensure that each test is run in isolation and does not interfere with the state of other tests.
Grouping Tests using describe()
Jest allows you to group tests together using the describe()
function. This function creates a test suite and helps with organizing and structuring your tests. You can nest describe()
blocks to create hierarchical test structures:
1 | describe('First set of tests', () => { |
This allows you to organize your tests into logical groups and provides a clear structure to your test code.
Testing Asynchronous Code
Testing asynchronous code can be challenging, as the test needs to wait for the async operation to complete. Jest provides several approaches to test asynchronous code, depending on the use case.
Callbacks
If you are working with code that uses callbacks, you can pass a callback parameter to the test function and invoke it when the async operation is complete. Jest will wait until done()
is called before finishing the test:
1 | function fetchData(callback) { |
Promises
For code that uses Promises, you can return a Promise from the test function. Jest will wait for the Promise to resolve or reject before finishing the test:
1 | function fetchData() { |
Async/await
If you are using async/await, you can use the async
keyword in the test function and await
to wait for the Promise to resolve:
1 | async function fetchData() { |
Jest will wait for the async operation to complete before finishing the test.
Mocking
Mocking allows you to replace parts of your code to isolate the behavior being tested. It is useful for testing functionality that depends on external systems, such as databases, networks, or files.
Jest provides built-in mocking capabilities that allow you to mock packages or individual functions.
Spy packages without affecting the functions code
You can use jest.spyOn()
to spy on the execution of a function in a package without modifying its behavior:
1 | const mathjs = require('mathjs'); |
This allows you to track if a function has been called and check the parameters it was called with.
Mock an entire package
Jest provides a convenient way to mock entire packages. You can create a __mocks__
folder in the root of your project and create a JavaScript file for each package you want to mock. For example, if you want to mock mathjs
, create a file called __mocks__/mathjs.js
with the following content:
1 | module.exports = { |
Then, in your test file, import mathjs
as usual, and the mock implementation will be used instead:
1 | const mathjs = require('mathjs'); |
Mock a single function
If you only need to mock a single function, you can use jest.fn()
:
1 | const mathjs = require('mathjs'); |
This replaces the original implementation of mathjs.log
with the mock implementation, allowing you to control the return value and track calls to the function.
Pre-built mocks
There are also pre-built mocks available for popular libraries that provide additional functionalities. For example, the jest-fetch-mock
package allows you to mock fetch()
calls and specify sample return values without interacting with the actual server in your tests.
Snapshot Testing
Snapshot testing is a powerful feature provided by Jest. It allows you to capture the output of a component or function and compare it against a previously saved snapshot. If the output has changed, Jest will highlight the difference and allow you to update the snapshot if the change is expected.
To use snapshot testing, you need to install react-test-renderer
(if you haven’t already) and write a test that renders your component or function and compares it to the saved snapshot:
1 | import React from 'react'; |
The first time you run this test, Jest will save the snapshot to a __snapshots__
folder. On subsequent test runs, Jest will compare the output to the saved snapshot. If there is a difference, Jest will display an error and give you the option to update the snapshot by pressing u
in the terminal.
Snapshot testing is particularly useful for UI components and can help you catch unintended changes to the UI. However, it should not be used as the sole method of testing complex logic or data transformations.
Conclusion
Jest is a powerful and easy-to-use testing framework for JavaScript that is especially well-suited for testing React applications. In this article, we explored the different features of Jest, including installation, writing tests, using matchers, setting up and tearing down test environments, mocking, and snapshot testing. By leveraging the features provided by Jest, you can write comprehensive tests that help ensure the quality and reliability of your JavaScript code.