SHIN
Node.js 실전 팁 20선
20편좋은 테스트는 "코드가 무엇을 해야 하는가"를 문서화하고, 리팩토링 시 안전망이 됩니다.
npm install -D vitest @vitest/coverage-v8 supertest// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'node',
globals: true,
coverage: {
provider: 'v8',
reporter: ['text', 'lcov'],
exclude: ['node_modules', 'dist', '**/*.d.ts'],
},
},
});// src/utils/formatDate.ts
export function formatDate(date: Date, locale = 'ko-KR'): string {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(date);
}// src/utils/formatDate.test.ts
import { describe, it, expect } from 'vitest';
import { formatDate } from './formatDate';
describe('formatDate', () => {
it('한국어 날짜 형식으로 변환한다', () => {
const date = new Date('2024-01-15');
expect(formatDate(date)).toBe('2024년 1월 15일');
});
it('영어 형식도 지원한다', () => {
const date = new Date('2024-01-15');
expect(formatDate(date, 'en-US')).toMatch(/January 15, 2024/);
});
});// tests/users.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import request from 'supertest';
import { app } from '../src/app';
import { prisma } from '../src/lib/prisma';
beforeAll(async () => {
await prisma.$executeRaw`DELETE FROM users WHERE email LIKE '%@test.com'`;
});
afterAll(async () => {
await prisma.$disconnect();
});
describe('POST /api/users', () => {
it('유효한 데이터로 유저를 생성한다', async () => {
const res = await request(app)
.post('/api/users')
.send({ email: 'test@test.com', name: '테스트' })
.expect(201);
expect(res.body).toMatchObject({ email: 'test@test.com' });
expect(res.body.id).toBeDefined();
});
it('이메일 없으면 400을 반환한다', async () => {
await request(app)
.post('/api/users')
.send({ name: '테스트' })
.expect(400);
});
});import { vi, describe, it, expect } from 'vitest';
import { sendEmail } from '../src/services/email';
vi.mock('../src/services/email', () => ({
sendEmail: vi.fn().mockResolvedValue({ id: 'mock-123' }),
}));
it('주문 생성 시 이메일을 발송한다', async () => {
await orderService.createOrder({ userId: 1, items: [] });
expect(sendEmail).toHaveBeenCalledWith(
expect.objectContaining({ subject: '주문 확인' })
);
});it('API 응답 구조가 변경되지 않는다', async () => {
const res = await request(app).get('/api/config').expect(200);
expect(res.body).toMatchSnapshot();
});{
"scripts": {
"test": "vitest run",
"test:coverage": "vitest run --coverage --coverage.thresholds.statements=80"
}
}테스트 피라미드: 단위 테스트(많음, 빠름) → 통합 테스트(중간) → E2E 테스트(적음, 느림). 각 레이어가 서로 다른 종류의 버그를 잡습니다.