Node.js 교재 · 22편 · 테스트

Node 테스트 — Jest로 유닛·API 테스트

Express API 의 결제 로직이 어제 잘 됐는데 오늘 깨졌다 — 그런 사고 막는 안전망.

테스트 러너와 supertest API 테스트 컨셉 일러스트

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·서버에는 충분. 신규 프로젝트는 둘 다 고민할 가치 있음.

구분Jestnode:test
설치npm 설치 필요내장
속도중간빠름
Mock풍부기본만
jsdom·ReactOK불편
적합한 곳풀스택·복잡 테스트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편.

© 2026 주나이테크(주) @JUNAITECH