[2026] Node.js Testing: Jest, Mocha, and Supertest
이 글의 핵심
Test Node.js apps with Jest matchers and mocks, async tests, Supertest for Express APIs, MongoDB memory server, integration tests, coverage thresholds, Mocha/Chai, and TDD patterns.
Introduction
Why test?
Testing verifies that code behaves as intended. Benefits:
- ✅ Catch bugs early: Before production
- ✅ Safer refactors: Regression safety net
- ✅ Documentation: Examples of usage
- ✅ Confidence: Ship changes with less fear
- ✅ Maintenance: Saves time over the long run Kinds of tests:
- Unit: Single functions or classes
- Integration: Multiple modules together
- E2E: Full system paths
1. Jest
Install
npm install --save-dev jest
package.json: 아래 코드는 json를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
First tests
아래 코드는 javascript를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = { add, subtract };
다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// math.test.js
const { add, subtract } = require('./math');
describe('Math', () => {
test('add sums two numbers', () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
expect(add(0, 0)).toBe(0);
});
test('subtract subtracts two numbers', () => {
expect(subtract(5, 3)).toBe(2);
expect(subtract(1, 1)).toBe(0);
expect(subtract(0, 5)).toBe(-5);
});
});
npm test
Matchers
다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
describe('Matcher examples', () => {
test('equality', () => {
expect(2 + 2).toBe(4);
expect({ name: 'Alice' }).toEqual({ name: 'Alice' });
});
test('truthiness', () => {
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect('hello').toBeDefined();
});
test('numbers', () => {
expect(10).toBeGreaterThan(5);
expect(10).toBeGreaterThanOrEqual(10);
expect(5).toBeLessThan(10);
expect(0.1 + 0.2).toBeCloseTo(0.3);
});
test('strings', () => {
expect('hello world').toMatch(/world/);
expect('hello').toContain('ell');
});
test('arrays and objects', () => {
const arr = ['apple', 'banana', 'cherry'];
expect(arr).toContain('banana');
expect(arr).toHaveLength(3);
const obj = { name: 'Alice', age: 25 };
expect(obj).toHaveProperty('name');
expect(obj).toHaveProperty('age', 25);
});
test('exceptions', () => {
expect(() => {
throw new Error('oops');
}).toThrow('oops');
});
});
2. Async tests
Promises
아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// api.js
async function fetchUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
return response.json();
}
module.exports = { fetchUser };
아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// api.test.js
const { fetchUser } = require('./api');
describe('API', () => {
test('fetches a user', async () => {
const user = await fetchUser(1);
expect(user).toHaveProperty('id', 1);
expect(user).toHaveProperty('name');
});
test('missing user rejects', async () => {
await expect(fetchUser(999)).rejects.toThrow();
});
});
Setup and teardown
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
describe('Database tests', () => {
beforeAll(async () => {
await connectDatabase();
});
beforeEach(async () => {
await clearDatabase();
});
afterEach(async () => {
/* cleanup */
});
afterAll(async () => {
await closeDatabase();
});
test('creates a user', async () => {
const user = await User.create({ name: 'Alice' });
expect(user).toHaveProperty('_id');
});
});
3. Mocks and spies
Mock functions
다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
describe('Mock functions', () => {
test('tracks calls', () => {
const mockFn = jest.fn();
mockFn('hello');
mockFn('world');
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith('hello');
expect(mockFn).toHaveBeenLastCalledWith('world');
});
test('return values', () => {
const mockFn = jest.fn();
mockFn.mockReturnValue(42);
expect(mockFn()).toBe(42);
mockFn.mockReturnValueOnce(1).mockReturnValueOnce(2).mockReturnValue(3);
expect(mockFn()).toBe(1);
expect(mockFn()).toBe(2);
expect(mockFn()).toBe(3);
expect(mockFn()).toBe(3);
});
});
Module mocks
아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// user.js
const axios = require('axios');
async function getUser(id) {
const response = await axios.get(`https://api.example.com/users/${id}`);
return response.data;
}
module.exports = { getUser };
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// user.test.js
const axios = require('axios');
const { getUser } = require('./user');
jest.mock('axios');
describe('getUser', () => {
test('returns user data', async () => {
const mockUser = { id: 1, name: 'Alice' };
axios.get.mockResolvedValue({ data: mockUser });
const user = await getUser(1);
expect(axios.get).toHaveBeenCalledWith('https://api.example.com/users/1');
expect(user).toEqual(mockUser);
});
test('propagates errors', async () => {
axios.get.mockRejectedValue(new Error('Network Error'));
await expect(getUser(1)).rejects.toThrow('Network Error');
});
});
Spies
아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
describe('Spies', () => {
test('wraps a method', () => {
const obj = { method: () => 'original' };
const spy = jest.spyOn(obj, 'method');
obj.method();
expect(spy).toHaveBeenCalled();
spy.mockRestore();
});
});
4. API tests with Supertest
npm install --save-dev supertest
다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// app.js
const express = require('express');
const app = express();
app.use(express.json());
app.get('/api/users', (req, res) => {
res.json({ users: [] });
});
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Name and email are required' });
}
res.status(201).json({ id: 1, name, email });
});
module.exports = app;
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// app.test.js
const request = require('supertest');
const app = require('./app');
describe('API', () => {
describe('GET /api/users', () => {
test('returns users', async () => {
const response = await request(app)
.get('/api/users')
.expect(200)
.expect('Content-Type', /json/);
expect(response.body).toHaveProperty('users');
expect(Array.isArray(response.body.users)).toBe(true);
});
});
describe('POST /api/users', () => {
test('creates a user', async () => {
const newUser = { name: 'Alice', email: 'alice@example.com' };
const response = await request(app)
.post('/api/users')
.send(newUser)
.expect(201)
.expect('Content-Type', /json/);
expect(response.body).toMatchObject(newUser);
expect(response.body).toHaveProperty('id');
});
test('returns 400 when invalid', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'Alice' })
.expect(400);
expect(response.body).toHaveProperty('error');
});
});
});
5. Database tests
MongoDB setup
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// test/setup.js
const mongoose = require('mongoose');
const { MongoMemoryServer } = require('mongodb-memory-server');
let mongoServer;
beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
await mongoose.connect(mongoServer.getUri());
});
afterAll(async () => {
await mongoose.disconnect();
await mongoServer.stop();
});
afterEach(async () => {
const collections = mongoose.connection.collections;
for (const key in collections) {
await collections[key].deleteMany();
}
});
jest.config.js: 아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
module.exports = {
testEnvironment: 'node',
setupFilesAfterEnv: ['<rootDir>/test/setup.js'],
coveragePathIgnorePatterns: ['/node_modules/']
};
Model tests
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// models/User.test.js
const User = require('../models/User');
describe('User model', () => {
test('creates a user', async () => {
const userData = {
name: 'Alice',
email: 'alice@example.com',
password: 'password123'
};
const user = await User.create(userData);
expect(user).toHaveProperty('_id');
expect(user.name).toBe(userData.name);
expect(user.email).toBe(userData.email);
});
test('duplicate email fails', async () => {
await User.create({
name: 'Alice',
email: 'alice@example.com',
password: 'password123'
});
await expect(User.create({
name: 'Bob',
email: 'alice@example.com',
password: 'password456'
})).rejects.toThrow();
});
test('required fields enforced', async () => {
await expect(User.create({ name: 'Alice' })).rejects.toThrow();
});
});
6. Integration tests
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// test/integration/users.test.js
const request = require('supertest');
const app = require('../../app');
const User = require('../../models/User');
describe('Users API integration', () => {
describe('POST /api/users', () => {
test('creates a user', async () => {
const userData = {
name: 'Alice',
email: 'alice@example.com',
password: 'password123'
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe(userData.name);
const user = await User.findById(response.body.id);
expect(user).toBeTruthy();
expect(user.email).toBe(userData.email);
});
});
describe('GET /api/users/:id', () => {
test('returns a user', async () => {
const user = await User.create({
name: 'Alice',
email: 'alice@example.com',
password: 'password123'
});
const response = await request(app)
.get(`/api/users/${user._id}`)
.expect(200);
expect(response.body.name).toBe(user.name);
expect(response.body.email).toBe(user.email);
});
test('404 when missing', async () => {
const response = await request(app)
.get('/api/users/507f1f77bcf86cd799439011')
.expect(404);
expect(response.body).toHaveProperty('error');
});
});
});
Auth integration
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const request = require('supertest');
const app = require('../../app');
const User = require('../../models/User');
const bcrypt = require('bcrypt');
describe('Auth API', () => {
describe('POST /auth/login', () => {
test('logs in with valid credentials', async () => {
const password = 'password123';
await User.create({
name: 'Alice',
email: 'alice@example.com',
password: await bcrypt.hash(password, 10)
});
const response = await request(app)
.post('/auth/login')
.send({ email: 'alice@example.com', password })
.expect(200);
expect(response.body).toHaveProperty('token');
expect(response.body.user.email).toBe('alice@example.com');
});
test('401 on wrong password', async () => {
await User.create({
name: 'Alice',
email: 'alice@example.com',
password: await bcrypt.hash('password123', 10)
});
await request(app)
.post('/auth/login')
.send({ email: 'alice@example.com', password: 'wrong' })
.expect(401);
});
});
describe('GET /api/profile', () => {
test('returns profile with token', async () => {
await User.create({
name: 'Alice',
email: 'alice@example.com',
password: await bcrypt.hash('password123', 10)
});
const loginResponse = await request(app)
.post('/auth/login')
.send({ email: 'alice@example.com', password: 'password123' });
const token = loginResponse.body.token;
const response = await request(app)
.get('/api/profile')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(response.body.user.email).toBe('alice@example.com');
});
test('401 without token', async () => {
await request(app).get('/api/profile').expect(401);
});
});
});
7. Mocha + Chai
npm install --save-dev mocha chai
{ "scripts": { "test": "mocha" } }
다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 변수 선언 및 초기화
const { expect } = require('chai');
const { add, subtract } = require('../math');
describe('Math', () => {
describe('add()', () => {
it('adds two numbers', () => {
expect(add(2, 3)).to.equal(5);
});
});
describe('subtract()', () => {
it('subtracts two numbers', () => {
expect(subtract(5, 3)).to.equal(2);
});
});
});
Chai
아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const { expect } = require('chai');
describe('Chai', () => {
it('equality', () => {
expect(2 + 2).to.equal(4);
expect({ name: 'Alice' }).to.deep.equal({ name: 'Alice' });
});
it('types', () => {
expect('hello').to.be.a('string');
expect(123).to.be.a('number');
expect([]).to.be.an('array');
});
});
8. Coverage
npm test -- --coverage
jest.config.js: 다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
module.exports = {
collectCoverageFrom: [
'src/**/*.js',
'!src/**/*.test.js',
'!src/index.js'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
9. Practical examples
User service
다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// services/userService.js
const User = require('../models/User');
const bcrypt = require('bcrypt');
class UserService {
async createUser(userData) {
const { email, password, name } = userData;
const existing = await User.findOne({ email });
if (existing) {
throw new Error('Email already registered');
}
const hashedPassword = await bcrypt.hash(password, 10);
return User.create({ email, password: hashedPassword, name });
}
async getUserById(id) {
const user = await User.findById(id).select('-password');
if (!user) throw new Error('User not found');
return user;
}
async updateUser(id, updates) {
const user = await User.findByIdAndUpdate(id, updates, {
new: true,
runValidators: true
}).select('-password');
if (!user) throw new Error('User not found');
return user;
}
async deleteUser(id) {
const user = await User.findByIdAndDelete(id);
if (!user) throw new Error('User not found');
return user;
}
}
module.exports = new UserService();
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const userService = require('./userService');
const User = require('../models/User');
describe('UserService', () => {
describe('createUser', () => {
test('creates user', async () => {
const userData = {
name: 'Alice',
email: 'alice@example.com',
password: 'password123'
};
const user = await userService.createUser(userData);
expect(user).toHaveProperty('_id');
expect(user.password).not.toBe(userData.password);
});
test('duplicate email', async () => {
await userService.createUser({
name: 'Alice',
email: 'alice@example.com',
password: 'password123'
});
await expect(userService.createUser({
name: 'Bob',
email: 'alice@example.com',
password: 'password456'
})).rejects.toThrow('Email already registered');
});
});
});
Auth API (extended)
See integration example above; add JWT verification checks that match your auth middleware.
10. Test doubles
Mock (email)
아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
jest.mock('nodemailer');
describe('sendEmail', () => {
test('sends mail', async () => {
const mockSendMail = jest.fn().mockResolvedValue({ messageId: '123' });
nodemailer.createTransport.mockReturnValue({ sendMail: mockSendMail });
await sendEmail('test@example.com', 'Subject', 'Body');
expect(mockSendMail).toHaveBeenCalledWith({
to: 'test@example.com',
subject: 'Subject',
text: 'Body'
});
});
});
Stub (payment)
아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
jest.mock('axios');
describe('processPayment', () => {
test('returns gateway data', async () => {
axios.post.mockResolvedValue({
data: { success: true, transactionId: 'txn_123' }
});
const result = await processPayment(10000);
expect(result.success).toBe(true);
expect(result.transactionId).toBe('txn_123');
});
});
11. Common issues
Async timeout
Timeout - Async callback was not invoked within the 5000 ms timeout
아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
test('slow op', async () => {
await slowOperation();
}, 10000);
// jest.config.js
module.exports = { testTimeout: 10000 };
Shared state
Use beforeEach to reset fixtures; avoid module-level mutable arrays unless cleared.
DB cleanup
다음은 간단한 javascript 코드 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
afterEach(async () => {
await User.deleteMany({});
await Post.deleteMany({});
});
12. Practical tips
Layout
아래 코드는 code를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
test/
├── unit/
├── integration/
├── e2e/
└── setup.js
Helpers
아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
async function createTestUser(overrides = {}) {
const defaults = {
name: 'Test User',
email: 'test@example.com',
password: await bcrypt.hash('password123', 10),
role: 'user'
};
return User.create({ ...defaults, ...overrides });
}
TDD sketch
Write a failing test, minimal pass, refactor, add edge-case tests.
Summary
Takeaways
- Jest: Default all-in-one choice
- Mocha + Chai: Flexible stack
- Supertest: HTTP-level API tests
- Mocks: Isolate I/O and time
- Coverage: Enforce thresholds thoughtfully
- TDD: Red–green–refactor when it helps
Framework comparison
| Framework | Pros | Cons |
|---|---|---|
| Jest | Batteries included, snapshots | Heavier |
| Mocha | Minimal core | Needs plugins |
| AVA | Parallel by default | Smaller ecosystem |
Practices
- Independent tests
- One main assertion per test (rule of thumb)
- Descriptive names
- Arrange–Act–Assert
- Test failure paths
- Mock external systems
Next steps
- Node.js deployment
- Node.js performance
- CI/CD pipelines