Introduction
Jest is a JavaScript Testing Framework known for its simplicity and ease of use. It is particularly favoured in projects utilising React, Angular, Vue, Node.js, Babel, and TypeScript.
This post is designed to provide a ‘mini-manual’ to setting up and efficiently utilising Jest in your JavaScript projects, covering everything from basic setup to advanced features.
1. Getting Started
Installation
To begin, ensure Node.js is installed on your system. If you’re starting a new project, initialise it using npm init
. Install Jest using npm by running the command:
npm install --save-dev jest
This command adds Jest as a development dependency in your project.
Configuration
In your package.json
, add a script to facilitate running tests with Jest. Under scripts
, add the test
script as follows:
"scripts": { "test": "jest" }
This setup allows you to run your Jest tests using npm test
.
Initial Test
Create a file named example.test.js
. In this file, write a simple test to ensure Jest is set up correctly:
test('true is true', () => { expect(true).toBe(true); });
Run npm test
and observe the output. If everything is set up correctly, Jest will run the test and report success.
2. Writing Tests
Basic Test Structure
Jest tests typically reside in files named *.test.js
or *.spec.js
. The basic structure of a Jest test involves describe
and test
(or it
) blocks. The describe
block groups together several related tests, and the test
block contains an individual test case.
Example of Test Suite
Here’s an example of a simple test suite for a hypothetical math utility module:
const mathUtils = require('./mathUtils');
describe('math operations', () => {
test('adds two numbers', () => {
expect(mathUtils.add(1, 2)).toBe(3);
});
test('subtracts two numbers', () => {
expect(mathUtils.subtract(5, 2)).toBe(3);
});
});
In this example, describe
groups the tests for addition and subtraction, while each test
block contains a specific case.
Test Naming
Choose clear, descriptive names for your test cases and suites. This practice makes it easier to understand what each test is verifying and aids in debugging when a test fails.
3. Running Tests
Basic Test Execution
To run your tests, simply execute npm test
in your project’s root directory. Jest automatically finds and runs all files with .test.js
or .spec.js
suffixes in your project.
Watching for Changes
While developing, you can run Jest in watch mode. This mode re-runs tests when it detects changes in your files. Run Jest in watch mode using:
npm test -- --watch
This mode boosts productivity by providing immediate feedback as you update your code and tests.
Filtering Test Runs
In larger projects, you might not want to run all tests every time. Jest allows you to specify a pattern or a filename to run a subset of your tests. For example:
npm test -- mathUtils
This command will only run tests in files that include mathUtils
in their filename.
4. Mocking
Purpose of Mocking
Mocking in testing is crucial for isolating the unit of code you’re testing. It replaces complex, unpredictable, or external parts of your application with simplified and controllable versions.
Creating Mock Functions
Use jest.fn()
to create a mock function. You can specify a return value or implement a mock implementation. For example:
const myMock = jest.fn(); myMock.mockReturnValue('Hello World');
This mock function, when called, will always return ‘Hello World’.
Mocking Modules
Jest allows you to mock entire modules, which is particularly useful for dependencies like APIs or databases. For instance:
jest.mock('axios');
This line replaces the axios
HTTP client module with a mock version for your tests.
5. Asynchronous Testing
Handling Async Code
Testing asynchronous code requires special consideration since you need to ensure Jest waits for the async code to complete. Jest provides several methods to handle this.
Callbacks
For traditional callback-based asynchronous code, Jest provides the done
function. Call done
when your async test is complete. For example:
test('async test with callback', done => {
setTimeout(() => {
expect(true).toBe(true);
done();
}, 1000);
});
In this test, Jest waits for the done
callback before considering the test complete.
Promises
If your code uses Promises, return the Promise from your test. Jest will wait for the Promise to resolve or reject. Here’s an example:
test('async test with promise', () => {
return fetchData().then(data => {
expect(data).toBe('expected data');
});
});
Jest will wait for the fetchData
promise to resolve before completing the test.
Async/Await
For modern JavaScript code, use async/await for cleaner asynchronous tests. Mark your test function as async
and await the asynchronous operations. For example:
test('async test with async/await', async () => {
const data = await fetchData();
expect(data).toBe('expected data');
});
This approach is clean and easy to read, making your asynchronous tests more straightforward.
6. Using Matchers
Introduction to Matchers
Matchers are methods provided by Jest to test values in different ways. They are used with expect
to form readable and expressive assertions.
Common Matchers
- .toBe(value): Tests exact equality.
- .toEqual(value): Tests value equality, useful for objects and arrays.
- .toContain(item): Checks if an array or string contains a specific item.
- .toBeTruthy(): Checks if a value is truthy.
Example Usage
Here’s how you might use these matchers in a test:
test('test object equality', () => {
const obj = { a: 1, b: 2 };
expect(obj).toEqual({ a: 1, b: 2 });
});
This test uses .toEqual
to check if obj
matches the expected object.
7. Configuration and Setup
jest.config.js
For more complex setups, you might need a Jest configuration file. Create a file named jest.config.js
in your project’s root. This file allows you to configure Jest extensively to suit your project’s needs.
Example Configuration
Here’s an example of what your jest.config.js
might look like:
module.exports = {
verbose: true,
setupFilesAfterEnv: ['./jest.setup.js'],
moduleNameMapper: {
'^@components/(.*)$': '<rootDir>/src/components/$1',
},
testEnvironment: 'node',
};
In this configuration:
verbose: true
makes Jest output more detailed information during runs.setupFilesAfterEnv
specifies scripts that run after the test environment is set up.moduleNameMapper
helps Jest understand module aliases, particularly useful in projects with complex directory structures.testEnvironment
specifies the environment in which the tests are run (e.g., a browser-like environment or a Node.js environment).
8. Advanced Features
Snapshot Testing
Snapshot testing is a Jest feature that captures the rendered output of components, enabling you to ensure your UI does not change unexpectedly. When you run a snapshot test, Jest creates a file with the rendered output. Subsequent test runs compare the new output to the stored snapshot.
Coverage Reports
Jest can generate coverage reports, showing which parts of your codebase are covered by tests. Enable this feature by adding --coverage
to your test script in package.json
:
"scripts": { "test": "jest --coverage" }
The coverage report helps identify untested parts of your code.
Interactive Watch Mode
Jest’s watch mode is an interactive testing mode that automatically re-runs relevant tests as you make changes to your code. It’s an efficient way to get immediate feedback on your changes.
9. Troubleshooting
Common Issues
- Tests Not Running: Ensure your test files are named correctly and placed in the right directories.
- Mocking Issues: Double-check your mock configurations and ensure they match the modules you’re trying to mock.
- Asynchronous Test Failures: Make sure you’re correctly handling asynchronous operations in your tests using callbacks, promises, or async/await.
Seeking Help
For issues not covered here, refer to Jest’s comprehensive documentation. Community forums and Q&A platforms like Stack Overflow are also great resources for specific problems.
10. Further Resources
- Jest Official Documentation: Jest Documentation offers in-depth guides, API references, and tutorials.
- Community Resources: Explore blog posts, tutorials, and videos for more practical insights and advanced use cases.
Summary
The list above cover most of the things I use on a regular basis. There are so many features that I will likely update this post from time to time as I need to be reminded of them.