Fixture ์์ฑ ๊ฐ์
createFixtureLoader
ํ์
์์ ํ ์ ์์๋์์ฑ ์ง์
์ฌ์ฌ์ฉ ๊ฐ๋ฅ
ํ ๋ฒ ์ ์์ฌ๋ฌ ํ
์คํธ ๊ณต์
๊ด๊ณ ์ฒ๋ฆฌ
BelongsTo ์๋ ํด๊ฒฐ์ค์ฒฉ๋ ๋ฐ์ดํฐ
์ ํ์ ๋ก๋ฉ
ํ์ํ ๊ฒ๋ง๋น ๋ฅธ ํ
์คํธ
createFixtureLoader
๊ธฐ๋ณธ ๊ตฌ์กฐ
๋ณต์ฌ
// api/src/testing/fixture.ts
import { createFixtureLoader } from "sonamu/test";
import { UserModel } from "@/models/user.model";
import { PostModel } from "@/models/post.model";
export const loadFixtures = createFixtureLoader({
// Fixture ์ด๋ฆ: ๋ก๋ ํจ์
user01: async () => {
const userModel = new UserModel();
const { user } = await userModel.create({
username: "john",
email: "[email protected]",
password: "password123",
});
return user;
},
admin: async () => {
const userModel = new UserModel();
const { user } = await userModel.create({
username: "admin",
email: "[email protected]",
password: "admin123",
role: "admin",
});
return user;
},
});
ํ์ ์ ์
๋ณต์ฌ
/**
* Fixture Loader Factory
*
* ํ
์คํธ์์ ์ฌ์ฉํ fixture๋ฅผ ๋ก๋ํ๋ ํจ์๋ฅผ ์์ฑ
*/
export function createFixtureLoader<T extends Record<string, () => Promise<unknown>>>(
loaders: T
) {
return async function loadFixtures<K extends keyof T>(
names: K[],
): Promise<{ [P in K]: Awaited<ReturnType<T[P]>> }> {
return Object.fromEntries(
await Promise.all(
names.map(async (name) => [name, await loaders[name]()])
),
);
};
}
- ํ์ ์์ : TypeScript๊ฐ fixture ์ด๋ฆ๊ณผ ๋ฐํ ํ์ ์ ์ถ๋ก
- ๋ณ๋ ฌ ๋ก๋ฉ:
Promise.all๋ก ์ฌ๋ฌ fixture ๋์ ๋ก๋ - ์ ํ์ : ํ์ํ fixture๋ง ๋ก๋
์ค์ ์์
๋จ์ Fixtures
๋ณต์ฌ
// api/src/testing/fixture.ts
import { createFixtureLoader } from "sonamu/test";
import { UserModel } from "@/models/user.model";
export const loadFixtures = createFixtureLoader({
// ์ผ๋ฐ ์ฌ์ฉ์
guestUser: async () => {
const userModel = new UserModel();
const { user } = await userModel.create({
username: "guest",
email: "[email protected]",
password: "password",
role: "guest",
});
return user;
},
// ํ๋ฆฌ๋ฏธ์ ์ฌ์ฉ์
premiumUser: async () => {
const userModel = new UserModel();
const { user } = await userModel.create({
username: "premium",
email: "[email protected]",
password: "password",
role: "premium",
subscription_expires_at: new Date("2025-12-31"),
});
return user;
},
// ๊ด๋ฆฌ์
adminUser: async () => {
const userModel = new UserModel();
const { user } = await userModel.create({
username: "admin",
email: "[email protected]",
password: "password",
role: "admin",
});
return user;
},
});
๊ด๊ณ๊ฐ ์๋ Fixtures
๋ณต์ฌ
// api/src/testing/fixture.ts
import { createFixtureLoader } from "sonamu/test";
import { UserModel } from "@/models/user.model";
import { PostModel } from "@/models/post.model";
import { CommentModel } from "@/models/comment.model";
export const loadFixtures = createFixtureLoader({
// ๊ธฐ๋ณธ ์ฌ์ฉ์
author: async () => {
const userModel = new UserModel();
const { user } = await userModel.create({
username: "author",
email: "[email protected]",
password: "password",
});
return user;
},
// ์์ฑ์์ ๊ฒ์๊ธ (author fixture ์ฌ์ฌ์ฉ)
authorPost: async () => {
// ๋ค๋ฅธ fixture ๋ก๋
const { author } = await loadFixtures(["author"]);
const postModel = new PostModel();
const { post } = await postModel.create({
title: "Author's First Post",
content: "This is the content",
author_id: author.id, // ๊ด๊ณ ์ค์
});
return post;
},
// ๊ฒ์๊ธ์ ๋๊ธ (authorPost fixture ์ฌ์ฌ์ฉ)
postComment: async () => {
const { author, authorPost } = await loadFixtures(["author", "authorPost"]);
const commentModel = new CommentModel();
const { comment } = await commentModel.create({
content: "Great post!",
post_id: authorPost.id,
author_id: author.id,
});
return comment;
},
});
์ฌ๋ฌ ๋ฐ์ดํฐ ์์ฑ
๋ณต์ฌ
export const loadFixtures = createFixtureLoader({
// 10๋ช
์ ์ฌ์ฉ์
users10: async () => {
const userModel = new UserModel();
const users = [];
for (let i = 1; i <= 10; i++) {
const { user } = await userModel.create({
username: `user${i}`,
email: `user${i}@example.com`,
password: "password",
});
users.push(user);
}
return users;
},
// ์ฌ์ฉ์์ ๊ทธ์ ๊ฒ์๊ธ๋ค
userWith5Posts: async () => {
const userModel = new UserModel();
const postModel = new PostModel();
const { user } = await userModel.create({
username: "blogger",
email: "[email protected]",
password: "password",
});
const posts = [];
for (let i = 1; i <= 5; i++) {
const { post } = await postModel.create({
title: `Post ${i}`,
content: `Content ${i}`,
author_id: user.id,
});
posts.push(post);
}
return { user, posts };
},
});
๋ณต์กํ ์๋๋ฆฌ์ค
๋ณต์ฌ
export const loadFixtures = createFixtureLoader({
// ์์ ํ ๋ธ๋ก๊ทธ ์๋๋ฆฌ์ค
blogScenario: async () => {
const userModel = new UserModel();
const postModel = new PostModel();
const commentModel = new CommentModel();
// 1. ์์ฑ์ ์์ฑ
const { user: author } = await userModel.create({
username: "author",
email: "[email protected]",
password: "password",
});
// 2. ๋
์๋ค ์์ฑ
const readers = await Promise.all([
userModel.create({
username: "reader1",
email: "[email protected]",
password: "password",
}),
userModel.create({
username: "reader2",
email: "[email protected]",
password: "password",
}),
]);
// 3. ๊ฒ์๊ธ ์์ฑ
const { post } = await postModel.create({
title: "Popular Post",
content: "This post has many comments",
author_id: author.id,
});
// 4. ๋๊ธ ์์ฑ
const comments = await Promise.all([
commentModel.create({
content: "First comment",
post_id: post.id,
author_id: readers[0].user.id,
}),
commentModel.create({
content: "Second comment",
post_id: post.id,
author_id: readers[1].user.id,
}),
commentModel.create({
content: "Author reply",
post_id: post.id,
author_id: author.id,
}),
]);
return {
author,
readers: readers.map(r => r.user),
post,
comments: comments.map(c => c.comment),
};
},
});
ํ ์คํธ์์ ์ฌ์ฉํ๊ธฐ
๊ธฐ๋ณธ ์ฌ์ฉ
๋ณต์ฌ
// api/src/models/post.model.test.ts
import { bootstrap, test } from "sonamu/test";
import { expect, vi } from "vitest";
import { PostModel } from "./post.model";
import { loadFixtures } from "@/testing/fixture";
bootstrap(vi);
test("์์ฑ์์ ๊ฒ์๊ธ ์กฐํ", async () => {
// Fixtures ๋ก๋
const { author, authorPost } = await loadFixtures(["author", "authorPost"]);
// ํ
์คํธ
const postModel = new PostModel();
const { posts } = await postModel.getPostsByAuthor(author.id);
expect(posts).toHaveLength(1);
expect(posts[0].id).toBe(authorPost.id);
});
ํ์ ์์ ์ฑ
๋ณต์ฌ
test("ํ์
์์ ํ fixture ์ฌ์ฉ", async () => {
// โ
IDE๊ฐ fixture ์ด๋ฆ ์๋์์ฑ ์ ๊ณต
const { author, authorPost } = await loadFixtures(["author", "authorPost"]);
// โ
ํ์
์ถ๋ก
author.username; // string
authorPost.title; // string
// โ ์กด์ฌํ์ง ์๋ fixture
const { invalid } = await loadFixtures(["invalid"]); // ํ์
์๋ฌ!
});
์ ํ์ ๋ก๋ฉ
๋ณต์ฌ
test("ํ์ํ fixture๋ง ๋ก๋", async () => {
// ํ์ํ ๊ฒ๋ง
const { author } = await loadFixtures(["author"]);
// authorPost๋ ๋ก๋ํ์ง ์์ โ ์์ฑ ๋น์ฉ ์ ์ฝ
});
test("์ฌ๋ฌ fixture ๋์ ๋ก๋", async () => {
// ๋ณ๋ ฌ๋ก ๋ก๋ (Promise.all)
const { author, premiumUser, adminUser } = await loadFixtures([
"author",
"premiumUser",
"adminUser",
]);
expect(author.role).toBe("user");
expect(premiumUser.role).toBe("premium");
expect(adminUser.role).toBe("admin");
});
๋ฒ ์คํธ ํ๋ํฐ์ค
1. ๋ช ํํ ์ด๋ฆ
๋ณต์ฌ
// โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: ์ฉ๋๊ฐ ๋ช
ํํ ์ด๋ฆ
export const loadFixtures = createFixtureLoader({
adminUser: async () => { /* ... */ },
guestUser: async () => { /* ... */ },
publishedPost: async () => { /* ... */ },
draftPost: async () => { /* ... */ },
premiumSubscription: async () => { /* ... */ },
});
// โ ์๋ชป๋ ๋ฐฉ๋ฒ: ๋ถ๋ช
ํํ ์ด๋ฆ
export const loadFixtures = createFixtureLoader({
user1: async () => { /* ... */ },
user2: async () => { /* ... */ },
post1: async () => { /* ... */ },
thing: async () => { /* ... */ },
});
2. ์ต์ ๋ฐ์ดํฐ
๋ณต์ฌ
// โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: ํ์ ํ๋๋ง
export const loadFixtures = createFixtureLoader({
basicUser: async () => {
const userModel = new UserModel();
const { user } = await userModel.create({
username: "user",
email: "[email protected]",
password: "password",
// ํ์ ํ๋๋ง
});
return user;
},
});
// โ ์๋ชป๋ ๋ฐฉ๋ฒ: ๋ถํ์ํ ํ๋๊น์ง
export const loadFixtures = createFixtureLoader({
complexUser: async () => {
const userModel = new UserModel();
const { user } = await userModel.create({
username: "user",
email: "[email protected]",
password: "password",
bio: "Very long bio...",
avatar: "https://...",
preferences: { /* ๋ณต์กํ ๊ฐ์ฒด */ },
// ํ
์คํธ์ ํ์ํ์ง ์์ ํ๋๋ค
});
return user;
},
});
3. Fixture ์ฌ์ฌ์ฉ
๋ณต์ฌ
// โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: ๋ค๋ฅธ fixture์์ ์ฌ์ฌ์ฉ
export const loadFixtures = createFixtureLoader({
user: async () => {
// ๊ธฐ๋ณธ ์ฌ์ฉ์
const userModel = new UserModel();
const { user } = await userModel.create({
username: "user",
email: "[email protected]",
password: "password",
});
return user;
},
userPost: async () => {
// user fixture ์ฌ์ฌ์ฉ
const { user } = await loadFixtures(["user"]);
const postModel = new PostModel();
const { post } = await postModel.create({
title: "Post",
content: "Content",
author_id: user.id,
});
return post;
},
userPostComment: async () => {
// userPost fixture ์ฌ์ฌ์ฉ (user๋ ์๋์ผ๋ก ํฌํจ๋จ)
const { user, userPost } = await loadFixtures(["user", "userPost"]);
const commentModel = new CommentModel();
const { comment } = await commentModel.create({
content: "Comment",
post_id: userPost.id,
author_id: user.id,
});
return comment;
},
});
4. ์ผ๊ด๋ ๊ตฌ์กฐ
๋ณต์ฌ
// โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: ์ผ๊ด๋ ๋ค์ด๋ฐ๊ณผ ๊ตฌ์กฐ
export const loadFixtures = createFixtureLoader({
// ์ฌ์ฉ์ ๊ด๋ จ
adminUser: async () => { /* ... */ },
guestUser: async () => { /* ... */ },
premiumUser: async () => { /* ... */ },
// ๊ฒ์๊ธ ๊ด๋ จ
publishedPost: async () => { /* ... */ },
draftPost: async () => { /* ... */ },
// ์๋๋ฆฌ์ค
blogScenario: async () => { /* ... */ },
forumScenario: async () => { /* ... */ },
});
๊ณ ๊ธ ํจํด
ํฉํ ๋ฆฌ ํจ์
๋ณต์ฌ
// ๋์ ์ผ๋ก fixture ์์ฑ
function createUserFixture(username: string, role: string) {
return async () => {
const userModel = new UserModel();
const { user } = await userModel.create({
username,
email: `${username}@example.com`,
password: "password",
role,
});
return user;
};
}
export const loadFixtures = createFixtureLoader({
adminUser: createUserFixture("admin", "admin"),
guestUser: createUserFixture("guest", "guest"),
moderatorUser: createUserFixture("moderator", "moderator"),
});
์ฃผ์์ฌํญ
Fixture ์์ฑ ์ ์ฃผ์์ฌํญ:
- Transaction ๊ธฐ๋ฐ: ๊ฐ ํ ์คํธ๋ง๋ค ์๋ ๋กค๋ฐฑ๋จ
- ์ต์ ๋ฐ์ดํฐ: ํ ์คํธ์ ํ์ํ ์ต์ํ์ ๋ฐ์ดํฐ๋ง ์์ฑ
- ๋ ๋ฆฝ์ฑ: Fixture๋ ์๋ก ๋ ๋ฆฝ์ ์ด์ด์ผ ํจ
- ์ฌ์ฌ์ฉ: ๊ณตํต fixture๋ฅผ ์ ๊ทน ํ์ฉ
- ๋ช ํํ ์ด๋ฆ: fixture ์ด๋ฆ์ ์ฉ๋๋ฅผ ๋ช ํํ๊ฒ ํํ