pnpm fixture 명령어는 테스트에 사용할 일관된 데이터 세트를 관리합니다. 개발 환경의 실제 데이터를 테스트 환경으로 복사하여, 안정적이고 반복 가능한 테스트를 수행할 수 있습니다.
기본 개념
Fixture는 테스트용 고정된 데이터 세트입니다:
- 일관성: 모든 테스트가 같은 초기 데이터로 시작
- 격리: 테스트 DB와 개발 DB 분리
- 재현성: 같은 조건에서 반복 테스트 가능
- 관계 보존: 외래키 관계가 유지됨
명령어
init - 테스트 DB 초기화
개발 DB의 스키마를 테스트 DB로 복사합니다.
실행 과정:
DUMP...
✓ Schema dumped from development_master
SYNC to (REMOTE) Fixture DB...
✓ Database myapp_fixture created
✓ Schema applied
SYNC to (LOCAL) Testing DB...
✓ Database myapp_test created
✓ Schema applied
Fixture initialization completed!
생성되는 데이터베이스:
- Fixture DB (원격): 공유 fixture 저장소
- Test DB (로컬): 로컬 테스트용 DB
init은 스키마만 복사합니다. 데이터는 포함되지 않습니다.
import - 데이터 가져오기
개발 DB에서 특정 레코드를 fixture로 저장합니다.
대화형 프롬프트가 표시됩니다:
? Please select entity: (Use arrow keys)
❯ User
Post
Comment
? Enter record IDs (comma-separated): 1,2,3
Importing fixtures...
✓ Imported User #1
✓ Imported User #2
✓ Imported User #3
✓ Imported related records (5 dependencies)
Fixture import completed!
자동으로 포함되는 관련 데이터:
- 외래키로 참조되는 레코드
- 관계 테이블의 레코드
- 역참조 레코드 (선택 가능)
sync - 동기화
저장된 fixture를 테스트 DB에 적용합니다.
실행 과정:
Syncing fixtures...
Clearing test database...
✓ Cleared all tables
Applying fixtures...
✓ Applied 3 users
✓ Applied 7 posts
✓ Applied 12 comments
✓ Applied 5 categories
Fixture sync completed!
테스트 실행 전에 sync를 호출하면 항상 깨끗한 상태에서 시작할 수 있습니다.
사용 워크플로우
1. 초기 설정
프로젝트 시작 시 한 번만 실행합니다.
# 테스트 DB 생성 및 스키마 복사
pnpm fixture init
2. 필요한 데이터 선택
테스트에 필요한 실제 데이터를 가져옵니다.
# User #1, #2, #3 가져오기
pnpm fixture import
# Entity: User
# IDs: 1,2,3
3. 테스트 작성
import { FixtureManager } from "sonamu/test";
beforeAll(async () => {
// Fixture 동기화
await FixtureManager.sync();
});
test("User를 이메일로 찾기", async () => {
// Fixture의 User #1 사용
const user = await UserModel.findByEmail("[email protected]");
expect(user).toBeDefined();
expect(user.id).toBe(1);
});
4. 테스트 실행
테스트가 실행될 때마다 fixture가 자동으로 동기화됩니다.
Fixture 파일
저장 위치
📁src/
📁fixtures/
📄JSONusers.json - User fixture
📄JSONposts.json - Post fixture
📄JSONcomments.json - Comment fixture
파일 형식
[
{
"id": 1,
"email": "[email protected]",
"name": "홍길동",
"created_at": "2024-01-15T00:00:00.000Z"
},
{
"id": 2,
"email": "[email protected]",
"name": "김철수",
"created_at": "2024-01-16T00:00:00.000Z"
}
]
특징:
- JSON 형식
- Entity별로 파일 분리
- 관계 데이터 자동 포함
- Git으로 버전 관리
관계 데이터 처리
외래키 자동 포함
Post를 import하면 연결된 User도 자동으로 포함됩니다.
pnpm fixture import
# Entity: Post
# IDs: 1
# 자동으로 포함됨:
# ✓ Post #1
# ✓ User #5 (author)
# ✓ Category #2 (category)
N:M 관계 처리
다대다 관계 테이블도 자동으로 포함됩니다.
pnpm fixture import
# Entity: Post
# IDs: 1
# 자동으로 포함됨:
# ✓ Post #1
# ✓ post_tags (중간 테이블)
# ✓ Tag #1, #2, #3 (연결된 태그들)
테스트 격리
각 테스트마다 초기화
describe("User CRUD", () => {
beforeEach(async () => {
// 각 테스트 전에 fixture 재적용
await FixtureManager.sync();
});
test("사용자 생성", async () => {
const user = await UserModel.create({
email: "[email protected]",
name: "신규사용자",
});
expect(user.id).toBeDefined();
});
test("사용자 삭제", async () => {
await UserModel.deleteById(1);
const user = await UserModel.findById(1);
expect(user).toBeNull();
});
});
격리 효과:
- 각 테스트가 독립적
- 테스트 순서 무관
- 병렬 실행 가능
트랜잭션 격리
import { Sonamu } from "sonamu";
test("트랜잭션 테스트", async () => {
await Sonamu.runScript(async () => {
// 트랜잭션 내에서 실행
const user = await UserModel.create({...});
const post = await PostModel.create({...});
// 테스트 완료 후 자동 롤백
});
});
실전 예제
복잡한 관계 테스트
describe("Post with Comments", () => {
beforeAll(async () => {
// Post #1 (+ User #1, Comments)
await FixtureManager.sync();
});
test("댓글이 있는 Post 조회", async () => {
const post = await PostModel.findById(1, {
include: ["comments"],
});
expect(post.comments).toHaveLength(3);
expect(post.comments[0].author_id).toBe(2);
});
});
특정 시나리오 데이터
describe("Premium User Features", () => {
beforeAll(async () => {
// Premium user만 import
await FixtureManager.importFixture("User", [10, 11, 12]);
await FixtureManager.sync();
});
test("프리미엄 기능 접근", async () => {
const user = await UserModel.findById(10);
expect(user.isPremium).toBe(true);
expect(await user.canAccessFeature("advanced")).toBe(true);
});
});
문제 해결
Fixture DB 연결 실패
문제: 원격 fixture DB에 접근 불가
Error: connect ECONNREFUSED
해결:
export default {
database: {
fixture: {
client: "pg",
connection: {
host: "fixture-db.example.com", // 올바른 호스트
database: "myapp_fixture",
user: "postgres",
password: process.env.FIXTURE_DB_PASSWORD,
},
},
},
};
관계 데이터 누락
문제: 외래키 에러 발생
Error: Foreign key constraint violation
해결:
# 관련 데이터를 먼저 import
pnpm fixture import
# Entity: User
# IDs: 1,2,3
pnpm fixture import
# Entity: Post
# IDs: 1,2,3 # User 1,2,3을 참조
Fixture 충돌
문제: ID 중복
Error: Duplicate entry '1' for key 'PRIMARY'
해결:
# Test DB 초기화
pnpm fixture init
# Fixture 재동기화
pnpm fixture sync
베스트 프랙티스
1. 최소한의 데이터
# ❌ 모든 데이터 import
pnpm fixture import
# IDs: 1-1000
# ✅ 필요한 데이터만
pnpm fixture import
# IDs: 1,2,3 # 대표 케이스만
2. 의미있는 데이터
3. 버전 관리
# Fixture 파일을 Git에 포함
git add src/fixtures/
git commit -m "Update test fixtures"
4. CI/CD 통합
.github/workflows/test.yml
jobs:
test:
steps:
- name: Setup Database
run: |
pnpm fixture init
pnpm fixture sync
- name: Run Tests
run: pnpm test
다음 단계