fe-test
1
总安装量
1
周安装量
#48042
全站排名
安装命令
npx skills add https://github.com/ingpdw/pdw-fe-dev-tool --skill fe-test
Agent 安装分布
mcpjam
1
claude-code
1
replit
1
junie
1
zencoder
1
Skill 文档
FE Test Generation
$ARGUMENTSë¡ ì ë¬ë íì¼ì ë¶ìíê³ ì ì í í
ì¤í¸ ì½ë를 ìì±íë¤.
í ì¤í¸ ìì± ì ì°¨
- ëì íì¼ ë¶ì: íì¼ì ì½ê³ exportë í¨ì/ì»´í¬ëí¸/í ì íì íë¤
- í ì¤í¸ ì í ê²°ì : íì¼ ì íì ë°ë¼ ì ì í í ì¤í¸ ì ëµì ì ííë¤
- í
ì¤í¸ íì¼ ìì±: co-location ìì¹ì ë°ë¼ ëì¼ ëë í 리ì
.test.ts(x)ìì± - ì¤í íì¸: ì¬ì©ììê²
vitest runì¤íì ìë´íë¤
íì¼ ì íë³ í ì¤í¸ ì ëµ
| íì¼ ì í | í ì¤í¸ ë구 | í ì¤í¸ ì´ì |
|---|---|---|
| ì í¸ë¦¬í° í¨ì | Vitest | ì ì¶ë ¥, ì£ì§ ì¼ì´ì¤, ìë¬ |
| 커ì¤í í | renderHook | ìí ë³í, ë°íê°, ì¬ì´ëì´íí¸ |
| UI ì»´í¬ëí¸ | RTL + Vitest | ë ëë§, ì¸í°ëì , ì ê·¼ì± |
| í¼ ì»´í¬ëí¸ | RTL + user-event | ì ë ¥, ì í¨ì± ê²ì¬, ì ì¶ |
| API í¸ì¶ | MSW + Vitest | ìì²/ìëµ, ìë¬ ì²ë¦¬, ë¡ë© ìí |
| íì´ì§ | RTL | íµí© ë ëë§, ë¼ì°í , ë°ì´í° íì |
| Zustand ì¤í ì´ | Vitest | ìí ë³ê²½, ì¡ì , ì ë í° |
í ì¤í¸ ì½ë 컨벤ì
기본 구조
import { describe, expect, it, vi, beforeEach } from "vitest";
describe("[í
ì¤í¸ ëì]", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("[기ë¥/ë©ìë]", () => {
it("[기ë ëìì ìì ]", () => {
// Arrange
// Act
// Assert
});
});
});
ì í¸ë¦¬í° í¨ì í ì¤í¸
import { describe, expect, it } from "vitest";
import { formatCurrency } from "./formatCurrency";
describe("formatCurrency", () => {
it("formats number with comma separators", () => {
expect(formatCurrency(1000)).toBe("â©1,000");
});
it("handles zero", () => {
expect(formatCurrency(0)).toBe("â©0");
});
it("handles negative numbers", () => {
expect(formatCurrency(-500)).toBe("-â©500");
});
it("rounds decimal places", () => {
expect(formatCurrency(99.999)).toBe("â©100");
});
});
ì»´í¬ëí¸ í ì¤í¸
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { Button } from "./Button";
describe("Button", () => {
it("renders with text", () => {
render(<Button>Click me</Button>);
expect(screen.getByRole("button", { name: "Click me" })).toBeInTheDocument();
});
it("calls onClick when clicked", async () => {
const user = userEvent.setup();
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click</Button>);
await user.click(screen.getByRole("button"));
expect(handleClick).toHaveBeenCalledOnce();
});
it("is disabled when disabled prop is true", () => {
render(<Button disabled>Click</Button>);
expect(screen.getByRole("button")).toBeDisabled();
});
it("applies variant classes", () => {
render(<Button variant="destructive">Delete</Button>);
expect(screen.getByRole("button")).toHaveClass("bg-destructive");
});
});
커ì¤í í í ì¤í¸
import { renderHook, act } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { useCounter } from "./useCounter";
describe("useCounter", () => {
it("initializes with default value", () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
it("initializes with provided value", () => {
const { result } = renderHook(() => useCounter(10));
expect(result.current.count).toBe(10);
});
it("increments count", () => {
const { result } = renderHook(() => useCounter());
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
it("decrements count", () => {
const { result } = renderHook(() => useCounter(5));
act(() => result.current.decrement());
expect(result.current.count).toBe(4);
});
});
í¼ í ì¤í¸
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { LoginForm } from "./LoginForm";
describe("LoginForm", () => {
const mockSubmit = vi.fn();
it("submits with valid data", async () => {
const user = userEvent.setup();
render(<LoginForm onSubmit={mockSubmit} />);
await user.type(screen.getByLabelText("Email"), "test@example.com");
await user.type(screen.getByLabelText("Password"), "password123");
await user.click(screen.getByRole("button", { name: /submit/i }));
await waitFor(() => {
expect(mockSubmit).toHaveBeenCalledWith({
email: "test@example.com",
password: "password123",
});
});
});
it("shows validation error for invalid email", async () => {
const user = userEvent.setup();
render(<LoginForm onSubmit={mockSubmit} />);
await user.type(screen.getByLabelText("Email"), "invalid");
await user.click(screen.getByRole("button", { name: /submit/i }));
expect(await screen.findByText(/valid email/i)).toBeInTheDocument();
expect(mockSubmit).not.toHaveBeenCalled();
});
});
API í¸ì¶ í ì¤í¸ (MSW)
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
import { fetchUsers } from "./api";
const server = setupServer(
http.get("/api/users", () => {
return HttpResponse.json([
{ id: "1", name: "Alice" },
{ id: "2", name: "Bob" },
]);
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe("fetchUsers", () => {
it("returns user list", async () => {
const users = await fetchUsers();
expect(users).toHaveLength(2);
expect(users[0].name).toBe("Alice");
});
it("handles server error", async () => {
server.use(
http.get("/api/users", () => {
return new HttpResponse(null, { status: 500 });
})
);
await expect(fetchUsers()).rejects.toThrow();
});
});
Zustand ì¤í ì´ í ì¤í¸
import { describe, expect, it, beforeEach } from "vitest";
import { useCartStore } from "./cartStore";
describe("cartStore", () => {
beforeEach(() => {
useCartStore.setState({ items: [], total: 0 });
});
it("adds item to cart", () => {
const { addItem } = useCartStore.getState();
addItem({ id: "1", name: "Product", price: 100 });
const { items } = useCartStore.getState();
expect(items).toHaveLength(1);
expect(items[0].name).toBe("Product");
});
it("calculates total", () => {
const { addItem } = useCartStore.getState();
addItem({ id: "1", name: "A", price: 100 });
addItem({ id: "2", name: "B", price: 200 });
expect(useCartStore.getState().total).toBe(300);
});
});
í ì¤í¸ ìì± ìì¹
- ì¬ì©ì ê´ì ì¼ë¡ í ì¤í¸: 구í ìì¸ê° ìë ëìì í ì¤í¸íë¤
- ì ê·¼ì± ì¿¼ë¦¬ ì°ì :
getByRole>getByLabelText>getByText>getByTestId - AAA í¨í´: Arrange â Act â Assert
- ë¨ì¼ ê²ì¦: íëì
itìì íëì ëìë§ ê²ì¦ - Mocking ìµìí: ì¸ë¶ ìì¡´ì±ë§ mock, ë´ë¶ 구íì mockíì§ ìì
- ì£ì§ ì¼ì´ì¤ í¬í¨: ë¹ ê°, null, ìë¬, ê²½ê³ê° í ì¤í¸
ì¤í ê·ì¹
- ì¸ìê° ìì¼ë©´ ì¬ì©ììê² í ì¤í¸ ëìì ì§ë¬¸íë¤
- ëì íì¼ì 먼ì ì½ê³ , exportë í목ì íì íë¤
- 기존 í ì¤í¸ íì¼ì´ ìì¼ë©´ ì½ê³ , ëë½ë ì¼ì´ì¤ë¥¼ ì¶ê°íë¤
- íë¡ì í¸ì í ì¤í¸ ì¤ì (vitest.config, setup íì¼)ì íì¸íê³ ë§ì¶ë¤
@testing-library/jest-dommatchers ì¬ì© ê°ë¥ ì¬ë¶ íì¸