Unit Testing with Jest
Jest is a zero-configuration testing framework developed by Meta (formerly Facebook). It ships with everything you need: test runner, assertion library, mocking capabilities, and code coverage tools. In this tutorial, you will learn how to write effective unit tests with Jest.
Installing Jest
Jest works in any JavaScript environment, Node.js, browsers, or React apps. The fastest way to add it to a project is via npm:
npm install --save-dev jest
Add a test script to your package.json:
{
"scripts": {
"test": "jest"
}
}
Or run Jest directly with npx:
npx jest
Jest defaults to finding files matching these patterns: *.test.js, *.spec.js, or in a __tests__ folder.
Writing Your First Test
Jest uses describe blocks to group related tests and test (or it) functions for individual test cases:
// math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = { add, subtract };
// math.test.js
const { add, subtract } = require('./math');
describe('Math functions', () => {
test('adds two numbers correctly', () => {
expect(add(2, 3)).toBe(5);
});
test('subtracts two numbers correctly', () => {
expect(subtract(10, 4)).toBe(6);
});
});
Run your tests:
npx jest
Output:
PASS ./math.test.js
Math functions
✓ adds two numbers correctly
✓ subtracts two numbers correctly
Understanding Matchers
Jest’s expect object provides matchers to assert values. Here are the most useful ones.
Equality Matchers
expect(value).toBe(5) // Strict equality (===)
expect(value).toEqual(obj) // Deep equality (for objects/arrays)
expect(value).toBeNull() // Strict null check
expect(value).toBeUndefined() // Strict undefined check
expect(value).toBeDefined() // Opposite of toBeUndefined
expect(value).toBeTruthy() // Truthy check
expect(value).toBeFalsy() // Falsy check
Numeric Matchers
expect(num).toBeGreaterThan(10) // >
expect(num).toBeGreaterThanOrEqual(10) // >=
expect(num).toBeLessThan(10) // <
expect(num).toBeLessThanOrEqual(10) // <=
expect(num).toBeCloseTo(0.1, 5) // Floating point comparison
String Matchers
expect(str).toMatch(/pattern/) // Regex match
expect(str).toContain('子') // Substring check
Array Matchers
expect(arr).toContain(item) // Array contains item
expect(arr).toHaveLength(3) // Array length check
Use .not to invert any matcher:
expect(result).not.toBeNull();
expect(items).not.toContain('bad');
Setup and Teardown
Often you need to run code before or after each test, or before and after all tests in a block:
describe('Database operations', () => {
let db;
// Run once before all tests
beforeAll(async () => {
db = await connectToDatabase();
});
// Run after all tests complete
afterAll(async () => {
await db.disconnect();
});
// Run before each individual test
beforeEach(() => {
db.clear();
});
// Run after each test
afterEach(() => {
db.resetMocks();
});
test('saves a user', async () => {
const user = await db.saveUser({ name: 'Alice' });
expect(user.id).toBeDefined();
});
});
Testing Asynchronous Code
Jest supports multiple patterns for testing async code.
Using async/await
test('fetches user data', async () => {
const user = await fetchUser(1);
expect(user).toEqual({
id: 1,
name: 'Alice',
email: 'alice@example.com'
});
});
Using Promises
test('fetches user data', () => {
return fetchUser(1).then(user => {
expect(user.name).toBe('Alice');
});
});
Using resolves/rejects
test('fetches user data', () => {
return expect(fetchUser(1)).resolves.toHaveProperty('name', 'Alice');
});
test('handles errors', () => {
return expect(fetchUser(999)).rejects.toThrow('User not found');
});
Mocking Functions
Jest makes it easy to mock functions, modules, and even timers.
Mocking a Single Function
const fetchData = require('./fetchData');
test('mocks a function', () => {
const mockFn = jest.fn();
mockFn.mockReturnValue(42);
expect(mockFn()).toBe(42);
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(1);
});
Mocking Module Dependencies
When your code imports a module, you can mock the entire module:
// api.js
const axios = require('axios');
module.exports = {
getUser: (id) => axios.get(`/users/${id}`)
};
// api.test.js
const api = require('./api');
const axios = require('axios');
jest.mock('axios');
test('fetches user successfully', async () => {
axios.get.mockResolvedValue({
data: { id: 1, name: 'Alice' }
});
const user = await api.getUser(1);
expect(user.data.name).toBe('Alice');
expect(axios.get).toHaveBeenCalledWith('/users/1');
});
Mock Implementations
const mockFn = jest.fn()
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');
console.log(mockFn()); // 'first call'
console.log(mockFn()); // 'second call'
console.log(mockFn()); // undefined (fallback)
Mocking Timers
For code that uses setTimeout or setInterval, Jest can fake the timers:
// timer.js
function delayedCallback(callback) {
setTimeout(() => {
callback('done');
}, 1000);
}
// timer.test.js
test('delayed callback', () => {
jest.useFakeTimers();
const callback = jest.fn();
delayedCallback(callback);
// Timer has not fired yet
expect(callback).not.toHaveBeenCalled();
// Fast-forward time
jest.advanceTimersByTime(1000);
// Now callback should have fired
expect(callback).toHaveBeenCalledWith('done');
jest.useRealTimers();
});
Test Organization Tips
- Follow the AAA pattern: Arrange (setup), Act (execute), Assert (verify)
- One expectation per test when possible, makes debugging easier
- Use descriptive test names:
adds positive numbersnottest1 - Keep tests isolated: each test should be independent
- Test edge cases: empty arrays, null values, negative numbers
Summary
You have learned the fundamentals of testing with Jest:
- Installing and configuring Jest
- Writing tests with
describeandtest - Using matchers like
toBe,toEqual,toMatch - Setup and teardown with
beforeEach,afterAll, etc. - Testing async code with async/await
- Mocking functions, modules, and timers
In the next tutorial, we will explore Unit Testing with Vitest, which offers a Jest-compatible API with better performance and native ESM support.