React Jest Testing
Master JavaScript testing with Jest framework
🃏 What is Jest?
Jest is a JavaScript testing framework created by Facebook. It works seamlessly with React and provides features like mocking, snapshots, and code coverage. Jest makes testing simple and enjoyable with zero configuration.
// sum.js
function sum(a, b) {
return a + b;
}
// sum.test.js
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
Result:
✅ PASS: Test passes because 1 + 2 equals 3
Jest Core Features
Matchers
Assert expected values
expect(value).toBe(3);
expect(value).toEqual({a: 1});
Mocks
Replace functions with test doubles
const mockFn = jest.fn();
mockFn.mockReturnValue(42);
Snapshots
Capture component output
expect(component)
.toMatchSnapshot();
Coverage
Track tested code percentage
jest --coverage
🔹 Basic Jest Test Structure
Jest tests use describe blocks to group related tests and test/it blocks for individual test cases. This structure keeps tests organized and readable.
// math.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export function multiply(a, b) {
return a * b;
}
// math.test.js
import { add, subtract, multiply } from './math';
describe('Math operations', () => {
test('adds two numbers', () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
});
test('subtracts two numbers', () => {
expect(subtract(5, 3)).toBe(2);
expect(subtract(0, 5)).toBe(-5);
});
test('multiplies two numbers', () => {
expect(multiply(2, 3)).toBe(6);
expect(multiply(-2, 3)).toBe(-6);
});
});
Output:
✅ PASS: Math operations (3 tests)
🔹 Common Jest Matchers
Matchers let you test values in different ways. Jest provides many built-in matchers for common testing scenarios like equality, truthiness, and comparisons.
describe('Jest Matchers', () => {
test('equality matchers', () => {
expect(2 + 2).toBe(4); // Strict equality
expect({ a: 1 }).toEqual({ a: 1 }); // Deep equality
});
test('truthiness matchers', () => {
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect(null).toBeNull();
expect(undefined).toBeUndefined();
});
test('number matchers', () => {
expect(10).toBeGreaterThan(5);
expect(5).toBeLessThan(10);
expect(0.1 + 0.2).toBeCloseTo(0.3); // Floating point
});
test('string matchers', () => {
expect('Hello World').toMatch(/World/);
expect('testing').toContain('test');
});
test('array matchers', () => {
const fruits = ['apple', 'banana', 'orange'];
expect(fruits).toContain('banana');
expect(fruits).toHaveLength(3);
});
test('exception matchers', () => {
const throwError = () => { throw new Error('Oops!'); };
expect(throwError).toThrow();
expect(throwError).toThrow('Oops!');
});
});
Output:
✅ PASS: All matcher tests pass
🔹 Mocking Functions
Mock functions let you test code that depends on external functions or APIs. You can control their behavior and verify how they're called.
// api.js
export function fetchUser(id) {
return fetch(`/api/users/${id}`).then(res => res.json());
}
export function greetUser(id, callback) {
fetchUser(id).then(user => {
callback(`Hello, ${user.name}!`);
});
}
// api.test.js
import { greetUser, fetchUser } from './api';
// Mock the fetchUser function
jest.mock('./api', () => ({
...jest.requireActual('./api'),
fetchUser: jest.fn()
}));
describe('Mocking Functions', () => {
test('calls callback with greeting', async () => {
// Setup mock return value
fetchUser.mockResolvedValue({ name: 'John' });
// Create mock callback
const mockCallback = jest.fn();
// Call function
await greetUser(1, mockCallback);
// Verify callback was called correctly
expect(mockCallback).toHaveBeenCalledWith('Hello, John!');
expect(mockCallback).toHaveBeenCalledTimes(1);
});
test('tracks function calls', () => {
const mockFn = jest.fn(x => x * 2);
mockFn(2);
mockFn(3);
// Check how many times called
expect(mockFn).toHaveBeenCalledTimes(2);
// Check what arguments were passed
expect(mockFn).toHaveBeenCalledWith(2);
expect(mockFn).toHaveBeenCalledWith(3);
// Check return values
expect(mockFn.mock.results[0].value).toBe(4);
expect(mockFn.mock.results[1].value).toBe(6);
});
});
Output:
✅ PASS: Mock functions work as expected
🔹 Setup and Teardown
Use setup and teardown functions to prepare test environments and clean up after tests. This ensures tests don't affect each other.
describe('Database Tests', () => {
let database;
// Runs before all tests in this describe block
beforeAll(() => {
database = connectToDatabase();
console.log('Database connected');
});
// Runs after all tests in this describe block
afterAll(() => {
database.disconnect();
console.log('Database disconnected');
});
// Runs before each test
beforeEach(() => {
database.clear();
console.log('Database cleared');
});
// Runs after each test
afterEach(() => {
console.log('Test completed');
});
test('adds user to database', () => {
database.addUser({ name: 'John' });
expect(database.getUsers()).toHaveLength(1);
});
test('removes user from database', () => {
database.addUser({ name: 'John' });
database.removeUser('John');
expect(database.getUsers()).toHaveLength(0);
});
});
Output:
Database connected → Test 1 → Test 2 → Database disconnected
🔹 Snapshot Testing
Snapshot tests capture component output and compare it to saved snapshots. They're great for detecting unexpected UI changes in React components.
// Button.jsx
import React from 'react';
function Button({ variant, children }) {
return (
<button className={`btn btn-${variant}`}>
{children}
</button>
);
}
export default Button;
// Button.test.jsx
import React from 'react';
import renderer from 'react-test-renderer';
import Button from './Button';
describe('Button Snapshots', () => {
test('renders primary button', () => {
const tree = renderer
.create(<Button variant="primary">Click Me</Button>)
.toJSON();
expect(tree).toMatchSnapshot();
});
test('renders secondary button', () => {
const tree = renderer
.create(<Button variant="secondary">Cancel</Button>)
.toJSON();
expect(tree).toMatchSnapshot();
});
});
Output:
✅ 2 snapshots written. Future runs will compare against these snapshots.
🔹 Testing Async Code
Jest provides multiple ways to test asynchronous code like promises and async/await. Always return promises or use async/await in tests.
// api.js
export function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ data: 'Hello World' });
}, 100);
});
}
export async function getUserData(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// api.test.js
import { fetchData, getUserData } from './api';
describe('Async Tests', () => {
// Method 1: Return promise
test('fetches data with promise', () => {
return fetchData().then(data => {
expect(data.data).toBe('Hello World');
});
});
// Method 2: Async/await
test('fetches data with async/await', async () => {
const data = await fetchData();
expect(data.data).toBe('Hello World');
});
// Method 3: resolves matcher
test('promise resolves correctly', () => {
return expect(fetchData()).resolves.toEqual({
data: 'Hello World'
});
});
// Testing rejected promises
test('handles errors', async () => {
const failingFetch = () => Promise.reject('Error!');
await expect(failingFetch()).rejects.toMatch('Error!');
});
});
Output:
✅ PASS: All async tests complete successfully
🔹 Running Jest Tests
Jest provides various commands to run tests in different modes. Use these commands to fit your development workflow.
Common Jest Commands:
- npm test - Run all tests
- npm test -- --watch - Run tests in watch mode
- npm test -- --coverage - Generate coverage report
- npm test Button - Run tests matching "Button"
- npm test -- --verbose - Show detailed test results
# Run all tests
npm test
# Watch mode - reruns tests on file changes
npm test -- --watch
# Coverage report
npm test -- --coverage
# Run specific test file
npm test Button.test.js
# Update snapshots
npm test -- -u