pnpm generate ๋ช
๋ น์ด๋ก ๋ชจ๋ธ ํ
์คํธ ํ์ผ์ ์๋ ์์ฑํฉ๋๋ค.
Scaffolding ๊ฐ์
์๋ ์์ฑ
model.test.ts ์์ฑ๊ธฐ๋ณธ ๊ตฌ์กฐ ํฌํจ
bootstrap ํฌํจ
ํ
์คํธ ํ๊ฒฝ ์๋ ์ค์ ์ฆ์ ์คํ ๊ฐ๋ฅ
CRUD ํ ์คํธ
๊ธฐ๋ณธ ํ
์คํธ ์ค์ผ๋ ํค์ปค์คํฐ๋ง์ด์ง ๊ฐ๋ฅ
ํ์ ์์
๋ชจ๋ธ๊ณผ ์ฐ๋์๋์์ฑ ์ง์
ํ ์คํธ ํ์ผ ์์ฑ
๋ช ๋ น์ด
๋ณต์ฌ
pnpm generate
{model}.test.ts ํ์ผ์ด ์์ฑ๋ฉ๋๋ค.
์์ฑ ์์น:
๋ณต์ฌ
api/src/models/
โโโ user.model.ts
โโโ user.model.test.ts # ์๋ ์์ฑ๋จ
โโโ post.model.ts
โโโ post.model.test.ts # ์๋ ์์ฑ๋จ
์์ฑ๋ ํ์ผ ๊ตฌ์กฐ
๋ณต์ฌ
// api/src/models/user.model.test.ts
import { bootstrap, test } from "sonamu/test";
import { expect, vi } from "vitest";
import { UserModel } from "./user.model";
// ํ
์คํธ ํ๊ฒฝ ์ด๊ธฐํ
bootstrap(vi);
test("์ฌ์ฉ์ ์์ฑ", async () => {
const userModel = new UserModel();
// ํ
์คํธ ์ฝ๋ ์์ฑ
const { user } = await userModel.create({
username: "john",
email: "[email protected]",
password: "password123",
});
expect(user.id).toBeGreaterThan(0);
expect(user.username).toBe("john");
});
test("์ฌ์ฉ์ ์กฐํ", async () => {
const userModel = new UserModel();
// ํ
์คํธ ๋ฐ์ดํฐ ์์ฑ
const { user } = await userModel.create({
username: "jane",
email: "[email protected]",
password: "password123",
});
// ์กฐํ ํ
์คํธ
const { user: found } = await userModel.getUser("C", user.id);
expect(found.id).toBe(user.id);
expect(found.username).toBe("jane");
});
ํ ์คํธ ์คํ
๋จ์ผ ํ์ผ ์คํ
๋ณต์ฌ
# ํน์ ๋ชจ๋ธ ํ
์คํธ๋ง ์คํ
pnpm vitest user.model.test.ts
์ ์ฒด ํ ์คํธ ์คํ
๋ณต์ฌ
# ๋ชจ๋ ํ
์คํธ ์คํ
pnpm test
# ๋๋
pnpm vitest
Watch ๋ชจ๋
๋ณต์ฌ
# ํ์ผ ๋ณ๊ฒฝ ์ ์๋ ์ฌ์คํ
pnpm vitest --watch
ํ ์คํธ ์์ฑ ๊ฐ์ด๋
CRUD ํ ์คํธ ํจํด
๋ณต์ฌ
import { bootstrap, test } from "sonamu/test";
import { expect, vi } from "vitest";
import { PostModel } from "./post.model";
bootstrap(vi);
// Create
test("๊ฒ์๊ธ ์์ฑ", async () => {
const postModel = new PostModel();
const { post } = await postModel.create({
title: "Test Post",
content: "Test Content",
author_id: 1,
});
expect(post.id).toBeGreaterThan(0);
expect(post.title).toBe("Test Post");
});
// Read
test("๊ฒ์๊ธ ์กฐํ", async () => {
const postModel = new PostModel();
// ํ
์คํธ ๋ฐ์ดํฐ ์์ฑ
const { post } = await postModel.create({
title: "Test Post",
content: "Test Content",
author_id: 1,
});
// ์กฐํ
const { post: found } = await postModel.getPost("C", post.id);
expect(found.id).toBe(post.id);
expect(found.title).toBe("Test Post");
});
// Update
test("๊ฒ์๊ธ ์์ ", async () => {
const postModel = new PostModel();
// ํ
์คํธ ๋ฐ์ดํฐ ์์ฑ
const { post } = await postModel.create({
title: "Original Title",
content: "Original Content",
author_id: 1,
});
// ์์
await postModel.update(post.id, {
title: "Updated Title",
});
// ํ์ธ
const { post: updated } = await postModel.getPost("C", post.id);
expect(updated.title).toBe("Updated Title");
expect(updated.content).toBe("Original Content"); // ๋ณ๊ฒฝ ์ ๋จ
});
// Delete
test("๊ฒ์๊ธ ์ญ์ ", async () => {
const postModel = new PostModel();
// ํ
์คํธ ๋ฐ์ดํฐ ์์ฑ
const { post } = await postModel.create({
title: "Test Post",
content: "Test Content",
author_id: 1,
});
// ์ญ์
await postModel.delete(post.id);
// ํ์ธ
const deleted = await postModel.findById("C", post.id);
expect(deleted).toBeNull();
});
๊ด๊ณ ํ ์คํธ
๋ณต์ฌ
test("์ฌ์ฉ์์ ๊ฒ์๊ธ ๊ด๊ณ", async () => {
const userModel = new UserModel();
const postModel = new PostModel();
// ์ฌ์ฉ์ ์์ฑ
const { user } = await userModel.create({
username: "author",
email: "[email protected]",
password: "password",
});
// ๊ฒ์๊ธ ์์ฑ
const { post } = await postModel.create({
title: "User's Post",
content: "Content",
author_id: user.id,
});
// ๊ด๊ณ ํ์ธ
expect(post.author_id).toBe(user.id);
// Subset "C"๋ก ์กฐํํ๋ฉด author ์ ๋ณด ํฌํจ
const { post: fullPost } = await postModel.getPost("C", post.id);
expect(fullPost.author?.username).toBe("author");
});
์๋ฌ ํ ์คํธ
๋ณต์ฌ
test("์ค๋ณต ์ด๋ฉ์ผ ๊ฒ์ฆ", async () => {
const userModel = new UserModel();
// ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์ ์์ฑ
await userModel.create({
username: "user1",
email: "[email protected]",
password: "password",
});
// ๊ฐ์ ์ด๋ฉ์ผ๋ก ๋ค์ ์์ฑ ์๋
await expect(
userModel.create({
username: "user2",
email: "[email protected]",
password: "password",
})
).rejects.toThrow("์ด๋ฏธ ์กด์ฌํ๋ ์ด๋ฉ์ผ์
๋๋ค");
});
test("์กด์ฌํ์ง ์๋ ๊ฒ์๊ธ ์กฐํ", async () => {
const postModel = new PostModel();
await expect(
postModel.getPost("C", 999999)
).rejects.toThrow("๊ฒ์๊ธ์ ์ฐพ์ ์ ์์ต๋๋ค");
});
ํ ์คํธ ๊ตฌ์กฐํ
์ฌ๋ฌ ํ ์คํธ ๊ทธ๋ฃนํ
๋ณต์ฌ
import { bootstrap, test } from "sonamu/test";
import { expect, vi } from "vitest";
import { describe } from "vitest";
import { UserModel } from "./user.model";
bootstrap(vi);
describe("์ฌ์ฉ์ ์์ฑ", () => {
test("์ ์์ ์ธ ์์ฑ", async () => {
const userModel = new UserModel();
const { user } = await userModel.create({
username: "john",
email: "[email protected]",
password: "password",
});
expect(user.id).toBeGreaterThan(0);
});
test("์ค๋ณต ์ด๋ฉ์ผ ๊ฒ์ฆ", async () => {
const userModel = new UserModel();
await userModel.create({
username: "user1",
email: "[email protected]",
password: "password",
});
await expect(
userModel.create({
username: "user2",
email: "[email protected]",
password: "password",
})
).rejects.toThrow();
});
});
describe("์ฌ์ฉ์ ์กฐํ", () => {
test("ID๋ก ์กฐํ", async () => {
const userModel = new UserModel();
const { user } = await userModel.create({
username: "test",
email: "[email protected]",
password: "password",
});
const { user: found } = await userModel.getUser("C", user.id);
expect(found.id).toBe(user.id);
});
test("์กด์ฌํ์ง ์๋ ์ฌ์ฉ์ ์กฐํ", async () => {
const userModel = new UserModel();
await expect(
userModel.getUser("C", 999999)
).rejects.toThrow();
});
});
๋ฒ ์คํธ ํ๋ํฐ์ค
1. ํ ์คํธ ๊ฒฉ๋ฆฌ
๊ฐ ํ ์คํธ๋ ๋ ๋ฆฝ์ ์ผ๋ก ์คํ๋์ด์ผ ํฉ๋๋ค.๋ณต์ฌ
// โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: ๊ฐ ํ
์คํธ๊ฐ ๋
๋ฆฝ์
test("ํ
์คํธ 1", async () => {
const userModel = new UserModel();
const { user } = await userModel.create({ /* ... */ });
// ํ
์คํธ ์ข
๋ฃ ํ ์๋ ๋กค๋ฐฑ
});
test("ํ
์คํธ 2", async () => {
const userModel = new UserModel();
// ๊นจ๋ํ DB ์ํ์์ ์์
const { user } = await userModel.create({ /* ... */ });
});
// โ ์๋ชป๋ ๋ฐฉ๋ฒ: ํ
์คํธ ๊ฐ ์์กด์ฑ
let sharedUserId: number;
test("ํ
์คํธ 1", async () => {
const userModel = new UserModel();
const { user } = await userModel.create({ /* ... */ });
sharedUserId = user.id; // โ ๋ค๋ฅธ ํ
์คํธ์ ๊ณต์
});
test("ํ
์คํธ 2", async () => {
const userModel = new UserModel();
const { user } = await userModel.getUser("C", sharedUserId); // โ ์คํจํจ!
});
2. ๋ช ํํ ํ ์คํธ ์ด๋ฆ
๋ณต์ฌ
// โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ
test("์ค๋ณต ์ด๋ฉ์ผ๋ก ์ฌ์ฉ์ ์์ฑ ์ ์๋ฌ ๋ฐ์", async () => {
// ...
});
// โ ์๋ชป๋ ๋ฐฉ๋ฒ
test("ํ
์คํธ 1", async () => {
// ...
});
3. Arrange-Act-Assert ํจํด
๋ณต์ฌ
test("๊ฒ์๊ธ ์์ ", async () => {
// Arrange (์ค๋น)
const postModel = new PostModel();
const { post } = await postModel.create({
title: "Original",
content: "Content",
author_id: 1,
});
// Act (์คํ)
await postModel.update(post.id, {
title: "Updated",
});
// Assert (๊ฒ์ฆ)
const { post: updated } = await postModel.getPost("C", post.id);
expect(updated.title).toBe("Updated");
});
์ฃผ์์ฌํญ
ํ
์คํธ ์์ฑ ์ ์ฃผ์์ฌํญ:
- ์๋ ์์ฑ ํ์ผ ์์ ๊ธ์ง: ์ฌ์์ฑ ์ ๋ฎ์ด์์์ง๋๋ค
- ๋ ๋ฆฝ์ ์ธ ํ ์คํธ: ํ ์คํธ ๊ฐ ์์กด์ฑ ์์ด์ผ ํจ
- Transaction ๊ธฐ๋ฐ: ๊ฐ ํ ์คํธ ์ข ๋ฃ ํ ์๋ ๋กค๋ฐฑ
- ๋น๋๊ธฐ ํ์: ๋ชจ๋ ํ ์คํธ๋ async ํจ์
- ๋ช ํํ ์ด๋ฆ: ํ ์คํธ๊ฐ ๋ฌด์์ ๊ฒ์ฆํ๋์ง ๋ช ํํ๊ฒ