๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
pnpm fixture ๋ช…๋ น์–ด๋Š” ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ•  ์ผ๊ด€๋œ ๋ฐ์ดํ„ฐ ์„ธํŠธ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์˜ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์œผ๋กœ ๋ณต์‚ฌํ•˜์—ฌ, ์•ˆ์ •์ ์ด๊ณ  ๋ฐ˜๋ณต ๊ฐ€๋Šฅํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ๊ฐœ๋…

Fixture๋Š” ํ…Œ์ŠคํŠธ์šฉ ๊ณ ์ •๋œ ๋ฐ์ดํ„ฐ ์„ธํŠธ์ž…๋‹ˆ๋‹ค:
  • ์ผ๊ด€์„ฑ: ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ™์€ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋กœ ์‹œ์ž‘
  • ๊ฒฉ๋ฆฌ: ํ…Œ์ŠคํŠธ DB์™€ ๊ฐœ๋ฐœ DB ๋ถ„๋ฆฌ
  • ์žฌํ˜„์„ฑ: ๊ฐ™์€ ์กฐ๊ฑด์—์„œ ๋ฐ˜๋ณต ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
  • ๊ด€๊ณ„ ๋ณด์กด: ์™ธ๋ž˜ํ‚ค ๊ด€๊ณ„๊ฐ€ ์œ ์ง€๋จ

๋ช…๋ น์–ด

init - ํ…Œ์ŠคํŠธ DB ์ดˆ๊ธฐํ™”

๊ฐœ๋ฐœ DB์˜ ์Šคํ‚ค๋งˆ๋ฅผ ํ…Œ์ŠคํŠธ DB๋กœ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค.
pnpm fixture init
์‹คํ–‰ ๊ณผ์ •:
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๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
pnpm fixture import
๋Œ€ํ™”ํ˜• ํ”„๋กฌํ”„ํŠธ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค:
? 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์— ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
pnpm fixture sync
์‹คํ–‰ ๊ณผ์ •:
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. ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

user.model.test.ts
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. ํ…Œ์ŠคํŠธ ์‹คํ–‰

pnpm test
ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค fixture๊ฐ€ ์ž๋™์œผ๋กœ ๋™๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค.

Fixture ํŒŒ์ผ

์ €์žฅ ์œ„์น˜

๐Ÿ“src/
๐Ÿ“fixtures/
๐Ÿ“„JSONusers.json - User fixture
๐Ÿ“„JSONposts.json - Post fixture
๐Ÿ“„JSONcomments.json - Comment fixture

ํŒŒ์ผ ํ˜•์‹

src/fixtures/users.json
[
  {
    "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
ํ•ด๊ฒฐ:
sonamu.config.ts
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. ์˜๋ฏธ์žˆ๋Š” ๋ฐ์ดํ„ฐ

// โœ… ํ…Œ์ŠคํŠธ ๋ชฉ์ ์ด ๋ช…ํ™•ํ•œ ๋ฐ์ดํ„ฐ
{
  "id": 1,
  "email": "[email protected]",
  "role": "admin",
  "name": "๊ด€๋ฆฌ์ž"
}
{
  "id": 2,
  "email": "[email protected]",
  "role": "user",
  "name": "์ผ๋ฐ˜์‚ฌ์šฉ์ž"
}

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

๋‹ค์Œ ๋‹จ๊ณ„