Introduction
Unit tests are essential for ensuring the reliability of your JavaScript code. Think of your app as a puzzle: a unit test verifies that one piece (a single function) works perfectly in isolation. In 2026, Jest—the most popular test runner—makes it fast and simple with zero-config setup, snapshots, and built-in mocks.
This beginner tutorial takes you from basics to advanced tests. Why does it matter? An undetected bug costs 100x more in production than during development. You'll learn to test sync and async functions, mock dependencies, and measure coverage. By the end, you'll have 80%+ test coverage without extra effort. Ready to bookmark this guide? Let's dive in step by step.
Prerequisites
- Node.js 20+ installed
- Basic JavaScript knowledge (functions, objects)
- An editor like VS Code
- Terminal (bash or PowerShell)
Initialize the Project
mkdir tests-unitaires-jest
cd tests-unitaires-jest
npm init -y
npm install --save-dev jest @types/jestThese commands create a project folder, initialize package.json, and install Jest as a dev dependency. The --save-dev flag keeps testing tools out of production. @types/jest adds TypeScript definitions for better autocomplete in VS Code.
Configure Jest in package.json
{
"name": "tests-unitaires-jest",
"version": "1.0.0",
"type": "module",
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"devDependencies": {
"jest": "^29.7.0",
"@types/jest": "^29.5.12"
}
}Enable ES modules with "type": "module" for modern imports. The scripts let you run npm test for all tests, --watch for auto-retesting on changes, and --coverage for a code coverage report. Copy-paste this full package.json.
Create a Function to Test
export function add(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new Error('Les arguments doivent être des nombres');
}
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export async function fetchUser(id) {
// Simulation API
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id, name: `User ${id}` });
}, 100);
});
}These functions cover real-world cases: add with error validation, multiply for simple math, and fetchUser as an async API simulation. They're exported for modularity. Test them in isolation to pinpoint bugs without external dependencies.
First Simple Unit Test
import { add, multiply } from './math.js';
describe('Math Utils', () => {
test('add devrait sommer deux nombres', () => {
expect(add(2, 3)).toBe(5);
expect(add(0, 0)).toBe(0);
expect(add(-1, 1)).toBe(0);
});
test('multiply devrait multiplier deux nombres', () => {
expect(multiply(2, 3)).toBe(6);
expect(multiply(0, 5)).toBe(0);
});
test('add devrait throw si arguments invalides', () => {
expect(() => add('a', 2)).toThrow('Les arguments doivent être des nombres');
});
});Your first test uses describe to group related tests and test for individual cases. expect().toBe() checks strict equality, while toThrow() verifies errors. Run npm test—all should pass. It's like a pre-flight checklist: each expect validates a scenario.
Async Test with Mocks
import { fetchUser } from './math.js';
const mockFetchUser = jest.fn();
// Mock global pour simuler
jest.mock('./math.js', () => ({
fetchUser: mockFetchUser
}));
beforeEach(() => {
mockFetchUser.mockClear();
});
describe('User Fetch', () => {
test('fetchUser devrait retourner un user async', async () => {
const mockUser = { id: 1, name: 'John' };
mockFetchUser.mockResolvedValue(mockUser);
const user = await fetchUser(1);
expect(user).toEqual(mockUser);
expect(mockFetchUser).toHaveBeenCalledTimes(1);
expect(mockFetchUser).toHaveBeenCalledWith(1);
});
test('fetchUser avec rejet', async () => {
mockFetchUser.mockRejectedValue(new Error('API down'));
await expect(fetchUser(1)).rejects.toThrow('API down');
});
});For async code, use async/await in tests and mockResolvedValue. jest.mock replaces the real function. beforeEach clears mocks between tests. Check calls with toHaveBeenCalledWith. Perfect for APIs without real network calls, avoiding flaky tests.
Run Tests and View Coverage
npm test
npm run test:watch
npm run test:coveragenpm test runs and validates all tests. --watch monitors files for auto-reruns (great for development). --coverage generates an HTML report in coverage/: aim for 80%+ line coverage. Open coverage/lcov-report/index.html to see uncovered lines.
Best Practices
- One test = one main assertion: Avoid 10
expects per test; split into atomic tests for easier debugging. - Name clearly:
test('should calculate 20% VAT', ...)>test('VAT'). - Mock externalities: APIs, databases, timers with
jest.useFakeTimers(). - Aim for >80% coverage: Enforce it in CI/CD.
- Fast tests first: Keep total under 100ms, no real I/O.
Common Mistakes to Avoid
- Forgetting async/await:
expect(promise).resolves.toBe()without await causes timeouts. - Not cleaning mocks: Use
beforeEach(mockClear())to prevent test pollution. - Order-dependent tests:
describeisolates; otherwise, they're flaky. - Object comparisons:
toBeis shallow; usetoEqualor snapshots withtoMatchSnapshot().
Next Steps
Level up to integration tests with Supertest or E2E with Playwright. Try Vitest for Vite/Next.js (faster). Join our Learni Dev courses to master TDD in React/Node. Official docs: Jest.