← Back to Node.js Menu | 🏠 Hub
npm install --save-dev jest supertest
# package.json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"jest": {
"testEnvironment": "node",
"testMatch": ["**/__tests__/**/*.js", "**/*.test.js"],
"collectCoverageFrom": ["src/**/*.js"]
}
}
# File naming
src/utils/math.js
src/utils/math.test.js ← Jest auto-finds these
# OR
src/__tests__/math.test.js
// src/utils/math.js
function add(a, b) { return a + b; }
function divide(a, b) {
if (b === 0) throw new Error("Cannot divide by zero");
return a / b;
}
function factorial(n) {
if (n < 0) throw new Error("Negative number");
if (n === 0) return 1;
return n * factorial(n - 1);
}
module.exports = { add, divide, factorial };
// src/utils/math.test.js
const { add, divide, factorial } = require('./math');
describe('Math utils', () => {
describe('add()', () => {
test('adds two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
test('adds negative numbers', () => {
expect(add(-1, -2)).toBe(-3);
});
test('adds zero', () => {
expect(add(5, 0)).toBe(5);
});
});
describe('divide()', () => {
test('divides correctly', () => {
expect(divide(10, 2)).toBe(5);
expect(divide(7, 2)).toBeCloseTo(3.5);
});
test('throws on division by zero', () => {
expect(() => divide(10, 0)).toThrow("Cannot divide by zero");
});
});
describe('factorial()', () => {
test.each([
[0, 1],
[1, 1],
[5, 120],
[10, 3628800],
])('factorial(%i) = %i', (input, expected) => {
expect(factorial(input)).toBe(expected);
});
test('throws on negative', () => {
expect(() => factorial(-1)).toThrow();
});
});
});
// src/routes/users.js (simplified)
const express = require('express');
const router = express.Router();
const users = [];
router.get('/', (req, res) => res.json(users));
router.post('/', (req, res) => {
const { name, email } = req.body;
if (!name || !email) return res.status(400).json({ error: 'Missing fields' });
const user = { id: Date.now(), name, email };
users.push(user);
res.status(201).json(user);
});
router.get('/:id', (req, res) => {
const user = users.find(u => u.id == req.params.id);
user ? res.json(user) : res.status(404).json({ error: 'Not found' });
});
module.exports = router;
// app.js (separate from server.js for testability)
const express = require('express');
const app = express();
app.use(express.json());
app.use('/api/users', require('./routes/users'));
module.exports = app;
// src/__tests__/users.test.js
const request = require('supertest');
const app = require('../app');
describe('Users API', () => {
let createdId;
test('GET /api/users — returns empty array', async () => {
const res = await request(app).get('/api/users');
expect(res.status).toBe(200);
expect(res.body).toBeInstanceOf(Array);
});
test('POST /api/users — creates user', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Ko Ko', email: 'ko@example.com' });
expect(res.status).toBe(201);
expect(res.body).toMatchObject({ name: 'Ko Ko', email: 'ko@example.com' });
expect(res.body.id).toBeDefined();
createdId = res.body.id;
});
test('POST /api/users — 400 on missing fields', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'No Email' });
expect(res.status).toBe(400);
expect(res.body.error).toBeTruthy();
});
test('GET /api/users/:id — finds user', async () => {
const res = await request(app).get(`/api/users/${createdId}`);
expect(res.status).toBe(200);
expect(res.body.name).toBe('Ko Ko');
});
test('GET /api/users/:id — 404 when not found', async () => {
const res = await request(app).get('/api/users/99999');
expect(res.status).toBe(404);
});
});
// Mock a module
jest.mock('../utils/emailService');
const emailService = require('../utils/emailService');
test('sends welcome email on register', async () => {
emailService.send = jest.fn().mockResolvedValue(true);
await registerUser({ name: 'Ko Ko', email: 'ko@test.com' });
expect(emailService.send).toHaveBeenCalledTimes(1);
expect(emailService.send).toHaveBeenCalledWith(
expect.objectContaining({ to: 'ko@test.com' })
);
});
// Mock Date.now()
jest.spyOn(Date, 'now').mockReturnValue(1234567890);
// Restore after test
afterEach(() => { jest.restoreAllMocks(); });
// Before/After hooks
beforeAll(async () => { /* setup DB */ });
afterAll(async () => { /* teardown DB */ });
beforeEach(async () => { /* reset state */ });
afterEach(async () => { /* cleanup */ });
| Matcher | Tests |
|---|---|
| toBe(val) | Strict equality (===) |
| toEqual(obj) | Deep equality (objects) |
| toMatchObject(obj) | Partial match (subset) |
| toBeTruthy() / toBeFalsy() | truthy/falsy |
| toBeNull() / toBeUndefined() | null/undefined |
| toBeGreaterThan(n) | number comparison |
| toBeCloseTo(n) | float comparison |
| toContain(item) | array/string contains |
| toHaveLength(n) | array/string length |
| toThrow(msg) | function throws |
| toHaveBeenCalled() | mock was called |
| toHaveBeenCalledWith(...) | mock called with args |
| resolves / rejects | async: await expect(fn()).resolves.toBe(x) |
← Node.js 08 | Next: Node.js 10 → Docker & Deploy →