Route Handler, Server Actions 통합 테스트 생성. "통합 테스트", "API 테스트", "Route Handler 테스트", "Server Action 테스트", "integration test" 언급 시 반드시 이 스킬을 활성화. node 환경, BDD(Given-When-Then) 방식.
Route Handler, Server Actions 통합 테스트 생성. node 환경, BDD(Given-When-Then) 방식.
각 Phase 시작 시 반드시 아래 형식으로 출력한다:
[🔍 분석 중...] Route Handler / Server Action 파악
[📋 시나리오 도출 중...] GWT 변환
[⚙️ 테스트 생성 중...] .integration.test.ts 작성
[▶️ 테스트 실행 중...] node 환경
[✅ 완료] 또는 [❌ 실패 - 원인 분류 중...]
[🔄 재시도 N/3] 정책 재검토 후 재생성 중...
단위 테스트와의 경계:
| 구분 | 단위 테스트 | 통합 테스트 |
|---|---|---|
| 대상 | 컴포넌트, 유틸 함수, Hook | Route Handler, Server Actions |
| 환경 | jsdom | node (실제 서버 환경) |
| 외부 의존성 | 전부 모킹 | DB/인증 모킹 선택 |
| 파일 네이밍 |
*.test.ts*.integration.test.ts |
$ARGUMENTS 없으면 "어떤 Route Handler 또는 Server Action의 통합 테스트를 작성할까요?" 질문package.json devDependencies에서 vitest 키 → Vitestjest 키 → Jestvitest.config.* 설정 파일 존재 여부 확인jest.config.* 설정 파일 존재 여부 확인package.json scripts 패턴 확인vitest.config.* 또는 jest.config.*에서 integration 프로젝트/환경 설정 여부 확인.
없으면 설정 추가 안내 (node 환경 분리 필요):
// vitest.config.ts — integration 프로젝트 설정 예시 (없으면 추가 안내)
{
test: {
name: 'integration',
include: ['**/*.integration.test.ts'],
environment: 'node',
}
}
app/api/**/route.ts → Route Handler'use server' 지시어 → Server Actions@/lib/db, prisma 등)next/headers, cookies(), headers())시나리오 계획 출력 후 사용자 확인.
app/api/users/route.ts
app/api/users/route.integration.test.ts ← 생성 위치
app/actions/user.ts
app/actions/user.integration.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { NextRequest } from 'next/server'
import { GET, POST } from './route'
// DB 모킹
vi.mock('@/lib/db', () => ({
db: {
user: {
findMany: vi.fn(),
create: vi.fn(),
findUnique: vi.fn(),
},
},
}))
import { db } from '@/lib/db'
describe('GET /api/users', () => {
beforeEach(() => { vi.clearAllMocks() })
it('사용자 목록을 JSON으로 반환한다', async () => {
// Given
const mockUsers = [{ id: 1, name: '홍길동' }]
vi.mocked(db.user.findMany).mockResolvedValue(mockUsers)
const request = new NextRequest('http://localhost:3000/api/users')
// When
const response = await GET(request)
const data = await response.json()
// Then
expect(response.status).toBe(200)
expect(data).toEqual(mockUsers)
})
it('DB 오류 시 500을 반환한다', async () => {
// Given
vi.mocked(db.user.findMany).mockRejectedValue(new Error('DB connection failed'))
const request = new NextRequest('http://localhost:3000/api/users')
// When
const response = await GET(request)
// Then
expect(response.status).toBe(500)
})
})
// params가 Promise 타입
const context = { params: Promise.resolve({ id: '1' }) }
const response = await GET(request, context)
vi.mock('next/headers', () => ({
cookies: vi.fn(),
headers: vi.fn(),
}))
// 테스트에서
vi.mocked(cookies).mockResolvedValue({
get: vi.fn().mockReturnValue({ value: 'valid-token' }),
} as any)
vi.mock('next/cache', () => ({
revalidatePath: vi.fn(),
}))
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { createUser } from './actions'
vi.mock('@/lib/db', () => ({
db: { user: { create: vi.fn() } },
}))
vi.mock('next/cache', () => ({
revalidatePath: vi.fn(),
}))
import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'
// FormData 헬퍼
function createFormData(data: Record<string, string>): FormData {
const formData = new FormData()
Object.entries(data).forEach(([key, value]) => formData.append(key, value))
return formData
}
describe('createUser', () => {
beforeEach(() => { vi.clearAllMocks() })
it('올바른 입력으로 사용자를 생성한다', async () => {
// Given
vi.mocked(db.user.create).mockResolvedValue({ id: 1, name: '홍길동', email: '[email protected]' })
const formData = createFormData({ name: '홍길동', email: '[email protected]' })
// When
const result = await createUser(null, formData)
// Then
expect(result.success).toBe(true)
expect(revalidatePath).toHaveBeenCalledWith('/users')
})
it('이름이 없으면 errors를 반환한다', async () => {
// Given
const formData = createFormData({ name: '', email: '[email protected]' })
// When
const result = await createUser(null, formData)
// Then
expect(result.errors?.name).toBeDefined()
})
})
# integration 프로젝트만 실행
{패키지매니저} test --project integration
# 특정 파일만
{패키지매니저} test -- --testPathPattern="integration.test"
통과 기준: 모든 테스트 pass (숫자 커버리지 측정 X)
[❌ 실패 - 원인 분류 중...]
원인 분류:
├─ 테스트 설계 문제 (mock 설정 오류, 잘못된 Request 구성, 정책 오해)
│ → [🔄 재시도 N/3] 정책 문서 재검토 → 시나리오 재도출 → 재생성
│
├─ 코드 버그 (Route Handler/Server Action 구현 오류)
│ → 루프 중단: "⚠️ 코드에 버그가 발견됐습니다: {내용}"
│
└─ 환경 문제 (node 환경 미설정, DB 연결, 인증 설정)
→ 루프 중단:
체크리스트:
- [ ] vitest.config.ts에 integration 프로젝트 설정 있나요?
- [ ] @/lib/db mock이 올바른 경로인가요?
- [ ] next/headers mock이 설정됐나요?
## 통합 테스트 생성 완료
### 생성된 파일
- {파일 경로}
### 테스트 케이스
| 대상 | Happy Path | Error | Validation | 합계 |
|------|:----------:|:-----:|:----------:|:----:|
| {이름} | {N} | {N} | {N} | {N} |
### 결과
| 테스트 | 결과 |
|--------|------|
| 전체 | {N}/{N} 통과 ✅ |
| 금지 | 이유 |
|---|---|
| 실제 DB/외부 API 호출 | 불안정, 부작용 |
| jsdom 환경에서 실행 | Route Handler는 node 환경 필수 |
| 실제 서버 기동 후 HTTP 요청 | E2E 범주, 통합 테스트 아님 |