Next.js 22편이 React 컴포넌트·E2E 라면, Node 백엔드는 다른 결의 테스트가 필요하다. 유닛(순수 함수 검증) + API 통합(HTTP 엔드포인트의 응답 모양) 두 가지가 핵심.
이번 편은 Node 진영 표준 Jest + supertest 조합. Node 22 부터 내장된 node:test 도 같이 비교.
1. Jest 설치와 첫 테스트
$ npm install -D jest @types/jest
$ npm install -D @jest/globals # ESM 환경 권장
// package.json
{
"scripts": {
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
}
}
ESM 프로젝트는 위 플래그 필요. CommonJS 면 그냥 jest. 이 설정 한 줄이 가장 헷갈리는 부분.
// utils.test.js
import { describe, it, expect } from '@jest/globals';
import { add, parseAmount } from './utils.js';
describe('add', () => {
it('두 수를 더한다', () => {
expect(add(1, 2)).toBe(3);
});
});
describe('parseAmount', () => {
it('₩1,234 → 1234', () => {
expect(parseAmount('₩1,234')).toBe(1234);
});
it('잘못된 입력은 throw', () => {
expect(() => parseAmount('abc')).toThrow();
});
});
2. supertest — Express API 테스트
실제 서버를 띄우지 않고 메모리 내에서 HTTP 요청 시뮬레이션. 빠르고 격리.
$ npm install -D supertest
// app.js — 분리해서 export
export function createApp(deps) {
const app = express();
app.use(express.json());
// 라우트 등록...
return app;
}
// server.js — 실제 서버
import { createApp } from './app.js';
const app = createApp({ db });
app.listen(3000);
// app.test.js
import { describe, it, expect, beforeEach } from '@jest/globals';
import request from 'supertest';
import { createApp } from './app.js';
describe('POST /api/users', () => {
let app, db;
beforeEach(() => {
db = { users: { create: jest.fn() } }; // mock
app = createApp({ db });
});
it('새 사용자 생성 후 201', async () => {
db.users.create.mockResolvedValue({ id: 'u1', email: '
[email protected]' });
const res = await request(app)
.post('/api/users')
.send({ email: '
[email protected]', password: 'pass1234' });
expect(res.status).toBe(201);
expect(res.body.id).toBe('u1');
expect(db.users.create).toHaveBeenCalledWith(
expect.objectContaining({ email: '
[email protected]' })
);
});
it('이메일 빠지면 400', async () => {
const res = await request(app).post('/api/users').send({});
expect(res.status).toBe(400);
});
});
핵심 — createApp 분리 — app 객체를 만드는 함수와 listen() 호출을 분리. 테스트는 createApp 만 호출해서 supertest 에 넘김. 실제 포트 안 잡고 메모리에서 동작 → 빠르고 병렬 가능.
3. Mock — 외부 의존성 격리
DB·외부 API 를 진짜로 호출하면 테스트가 느리고 flaky. Mock 으로 대체.
// 함수 mock
const mockDb = {
users: {
findById: jest.fn().mockResolvedValue({ id: 'u1', name: '박준성' }),
create: jest.fn().mockImplementation(({ data }) => ({ id: 'new', ...data })),
},
};
// 모듈 전체 mock
jest.mock('./email-service.js', () => ({
sendEmail: jest.fn().mockResolvedValue({ messageId: 'mock-id' }),
}));
// 외부 fetch mock (Node 22+)
global.fetch = jest.fn().mockResolvedValue({
ok: true,
json: async () => ({ data: 'mocked' }),
});
각 테스트 끝나면 jest.clearAllMocks() 또는 beforeEach 에서 새 mock 만들기. 상태 누수 방지.
4. Coverage — 어디가 안 가렸는지 보기
$ npm test -- --coverage
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 84.21 | 72.50 | 88.00 | 84.50 |
utils.js | 100.00 | 100.00 | 100.00 | 100.00 |
app.js | 76.92 | 60.00 | 80.00 | 77.50 | 45,52-58
4가지 지표 — Stmts(실행된 문장)·Branch(if/else 양쪽)·Funcs(호출된 함수)·Lines. Branch 가 보통 가장 낮다 (예외 분기까지 다 안 타니까). 70~80% 가 현실적 목표.
커버리지 함정 — 숫자에 집착하지 말 것. 100% 커버리지 코드가 결국 사고 안 막는 케이스 많음. 핵심 비즈니스 로직(결제·인증·계산) 위주로 두툼하게, 단순 데이터 표시 코드는 얕게. 회사 회귀 사고 분석에서 자주 깨지는 부분 우선.
5. Node 22 내장 test — Jest 대안
Node 18+ 부터 내장 node:test. 가볍고 빠르고 패키지 0개.
// utils.test.js — 내장
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { add } from './utils.js';
describe('add', () => {
it('두 수 더하기', () => {
assert.strictEqual(add(1, 2), 3);
});
});
// 실행
$ node --test --experimental-test-coverage utils.test.js
Jest 보다 적은 기능(스냅샷·jsdom·mock 라이브러리 등) — 그러나 가벼운 CLI·서버에는 충분. 신규 프로젝트는 둘 다 고민할 가치 있음.
| 구분 | Jest | node:test |
| 설치 | npm 설치 필요 | 내장 |
| 속도 | 중간 | 빠름 |
| Mock | 풍부 | 기본만 |
| jsdom·React | OK | 불편 |
| 적합한 곳 | 풀스택·복잡 테스트 | CLI·작은 서버 |
요약 — 22편 좌표
여기까지 정리. Jest + supertest 가 Node 백엔드 표준. createApp 을 함수로 분리해 supertest 에 주입. DB·외부 API 는 mock(jest.fn 또는 jest.mock). Coverage 는 70~80% 목표지만 핵심 로직 우선. Node 22 내장 node:test 는 CLI·간단 서버에 충분. CI 표준은 npm test -- --ci --coverage. 다음 편에서 worker_threads·cluster 로 CPU 활용.
다음 편 예고 — cluster·worker_threads
CPU 코어 최대 활용. 23편.