In modern JavaScript development, writing clean and reliable code is only half the battle. The other half is ensuring that the code behaves as expected — now and in the future. This is where unit testing comes into play, and among the many testing frameworks available, Jest has emerged as the most popular choice for JavaScript and React developers.
In this blog, we’ll take a deep dive into unit testing with Jest, understand how it works, write various types of tests, and follow best practices to ensure maintainability and scalability of your test suites.
What is Unit Testing?
Unit testing involves testing individual pieces (units) of code in isolation to verify they work as intended. These “units” are typically functions or methods in your codebase.
Why Unit Test?
- Detect bugs early
- Ensure code reliability
- Refactor safely
- Improve documentation
- Enable CI/CD workflows
What is Jest?
Jest is a delightful JavaScript Testing Framework developed by Meta (Facebook). It works out-of-the-box for most JavaScript projects and includes powerful features like:
- Zero configuration
- Test runners
- Snapshot testing
- Code coverage
- Mocking
- Parallel test execution
Jest is especially popular in React projects, but it works just as well in Node.js and vanilla JavaScript projects.
Getting Started with Jest
1. Install Jest
To install Jest in your project, run:
npm install --save-dev jest
Update the package.json
to add a test script:
{
"scripts": {
"test": "jest"
}
}
Create a sum.js
file:
function sum(a, b) {
return a + b;
}
module.exports = sum;
Now create a test file named sum.test.js
:
const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
Run your test:
npm test
Anatomy of a Jest Test
test('description of the test', () => {
// Arrange: Setup the necessary data
// Act: Call the function under test
// Assert: Check the expected result
});
Or using describe
blocks for grouping:
describe('sum function', () => {
it('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
});
Common Matchers in Jest
Jest provides rich matchers via the expect()
API.
Matcher | Description |
---|---|
.toBe() | Exact match (===) |
.toEqual() | Deep equality (objects, arrays) |
.toBeNull() | Checks for null |
.toBeTruthy() | Checks if value is true in boolean |
.toContain() | Array or string contains value |
.toThrow() | Function throws an error |
Example:
expect([1, 2, 3]).toContain(2);
expect(() => someFunc()).toThrow('error message');
Testing Async Code
Async/Await Example:
async function fetchData() {
return 'data';
}
test('fetches data', async () => {
const data = await fetchData();
expect(data).toBe('data');
});
Using .resolves
and .rejects
:
ttest('resolves to data', () => {
return expect(fetchData()).resolves.toBe('data');
});
Mocking Functions in Jest
Jest makes mocking easy using jest.fn()
.
Manual Mock Example:
const mockFn = jest.fn();
mockFn('hello');
expect(mockFn).toHaveBeenCalledWith('hello');
Mocking External Modules:
Suppose you want to mock axios
:
jest.mock('axios');
const axios = require('axios');
axios.get.mockResolvedValue({ data: 'Mocked Data' });
Testing React Components (Bonus)
With React, use @testing-library/react
alongside Jest:
npm install --save-dev @testing-library/react
// Button.js
export default function Button({ onClick }) {
return <button onClick={onClick}>Click me</button>;
}
// Button.test.js
import { render, fireEvent } from '@testing-library/react';
import Button from './Button';
test('calls onClick when clicked', () => {
const handleClick = jest.fn();
const { getByText } = render(<Button onClick={handleClick} />);
fireEvent.click(getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
Generating Code Coverage Report
Run Jest with the --coverage
flag:
npm test -- --coverage
It will generate a coverage report like this:
File | % Stmts | % Branch | % Funcs | % Lines
-----------|---------|----------|---------|---------
sum.js | 100 | 100 | 100 | 100
Best Practices for Writing Unit Tests
- Test one thing per test case – Keep tests focused.
- Use descriptive test names – Improve readability.
- Arrange-Act-Assert pattern – Structure test cases clearly.
- Avoid testing implementation details – Focus on behavior.
- Mock dependencies, not your own code – Mock only external APIs.
- Keep tests fast and isolated – They shouldn’t rely on DBs or APIs.
- Run tests frequently – Integrate with CI pipelines.
TDD with Jest (Test Driven Development)
- Write a failing test – Define the expected behavior.
- Write the minimal code – Make the test pass.
- Refactor confidently – Rely on passing tests for validation.
This encourages cleaner, well-designed, and better-tested code.
Useful Jest Plugins & Tools
jest-watch-typeahead
– Improve test selection.jest-extended
– More matchers.jest-runner-eslint
– Linting as test.
Sample Project Structure for Testing
my-app/
├── src/
│ ├── utils/
│ │ └── sum.js
│ ├── components/
│ │ └── Button.js
├── tests/
│ ├── unit/
│ │ └── sum.test.js
│ ├── integration/
│ │ └── api.test.js
Separate test types for better organization and maintainability.
Conclusion
Unit testing isn’t just a safety net — it’s a way to build confidence, write robust code, and move fast without breaking things. With Jest, testing becomes seamless, powerful, and even enjoyable.
Whether you’re building small utility functions or complex React applications, integrating Jest into your workflow is one of the smartest investments you can make.
Ready to Test Smarter?
Try converting one of your existing JavaScript files into a testable module and write a simple Jest test for it. Practice will make you more fluent, and Jest will become a natural part of your dev toolkit.