home / skills / pluxity / pf-frontend / pf-test-hook
/.claude/skills/pf-test-hook
This skill generates comprehensive tests for custom hooks using Vitest and Testing Library, covering initialization, actions, prop changes, async hooks, and
npx playbooks add skill pluxity/pf-frontend --skill pf-test-hookReview the files below or copy the command above to add this skill to your agents.
---
name: pf-test-hook
description: 커스텀 훅 테스트 생성. "훅 테스트", "hook 테스트" 요청 시 사용.
allowed-tools: Read, Write, Glob
---
# PF 커스텀 훅 테스트 생성기
$ARGUMENTS 훅에 대한 테스트 파일을 생성합니다.
---
## 테스트 구조 (Vitest + @testing-library/react)
```tsx
import { renderHook, act, waitFor } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { useCounter } from "./useCounter";
describe("useCounter", () => {
// 1. 초기 상태 테스트
describe("초기화", () => {
it("기본값 0으로 시작한다", () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
it("initialValue로 시작한다", () => {
const { result } = renderHook(() => useCounter(10));
expect(result.current.count).toBe(10);
});
});
// 2. 액션 테스트
describe("액션", () => {
it("increment가 count를 1 증가시킨다", () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
it("decrement가 count를 1 감소시킨다", () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(4);
});
it("reset이 initialValue로 되돌린다", () => {
const { result } = renderHook(() => useCounter(10));
act(() => {
result.current.increment();
result.current.increment();
result.current.reset();
});
expect(result.current.count).toBe(10);
});
});
// 3. props 변경 테스트
describe("props 변경", () => {
it("initialValue 변경 시 reset하면 새 값으로", () => {
const { result, rerender } = renderHook(
({ initial }) => useCounter(initial),
{ initialProps: { initial: 0 } }
);
rerender({ initial: 100 });
act(() => {
result.current.reset();
});
expect(result.current.count).toBe(100);
});
});
});
```
---
## 비동기 훅 테스트
```tsx
import { renderHook, waitFor } from "@testing-library/react";
describe("useFetch", () => {
beforeEach(() => {
vi.resetAllMocks();
});
it("데이터를 성공적으로 가져온다", async () => {
const mockData = { id: 1, name: "Test" };
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(mockData),
});
const { result } = renderHook(() => useFetch("/api/test"));
// 로딩 상태 확인
expect(result.current.isLoading).toBe(true);
// 데이터 로드 대기
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.data).toEqual(mockData);
expect(result.current.error).toBeNull();
});
it("에러를 처리한다", async () => {
global.fetch = vi.fn().mockRejectedValue(new Error("Network error"));
const { result } = renderHook(() => useFetch("/api/test"));
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.data).toBeNull();
expect(result.current.error).toBe("Network error");
});
});
```
---
## Zustand Store를 사용하는 훅 테스트
```tsx
import { renderHook, act } from "@testing-library/react";
import { useAuthStore } from "@/stores/auth.store";
// Store 초기화
beforeEach(() => {
useAuthStore.setState({ user: null, isLoading: false });
});
describe("useAuth", () => {
it("로그인 성공 시 user가 설정된다", async () => {
const mockUser = { id: 1, name: "Test User" };
vi.spyOn(authService, "login").mockResolvedValue(mockUser);
const { result } = renderHook(() => useAuth());
await act(async () => {
await result.current.login("[email protected]", "password");
});
expect(result.current.user).toEqual(mockUser);
expect(result.current.isAuthenticated).toBe(true);
});
});
```
---
## Context Provider가 필요한 훅 테스트
```tsx
import { renderHook } from "@testing-library/react";
import { ReactNode } from "react";
const wrapper = ({ children }: { children: ReactNode }) => (
<ThemeProvider defaultTheme="light">
<AuthProvider>
{children}
</AuthProvider>
</ThemeProvider>
);
describe("useTheme", () => {
it("현재 테마를 반환한다", () => {
const { result } = renderHook(() => useTheme(), { wrapper });
expect(result.current.theme).toBe("light");
});
});
```
---
## 테스트 체크리스트
- [ ] 초기 상태가 올바른가
- [ ] 모든 반환값이 테스트되는가
- [ ] 액션이 상태를 올바르게 변경하는가
- [ ] props/deps 변경에 반응하는가
- [ ] cleanup이 제대로 동작하는가 (useEffect)
- [ ] 에러 케이스가 처리되는가
This skill generates TypeScript test files for React custom hooks using Vitest and @testing-library/react. It produces structured tests for initialization, actions, props changes, async behavior, Zustand stores, and context-wrapped hooks, matching common patterns used by PF DEV. Use the skill by requesting "hook 테스트" or "훅 테스트" and providing the hook name and signature.
Given a hook name and optional details (parameters, return shape, dependencies, async behavior, store or context needs), the skill scaffolds test cases and utilities. It emits renderHook-based tests with act, waitFor, mocks for fetch or services, Zustand store resets, and a wrapper component for context providers. The output follows consistent sections: initialization, actions, props/dep changes, async flows, and cleanup checks.
What inputs do I need to provide?
Supply the hook name, parameter list and expected return shape; indicate if it uses fetch, stores, or context providers.
Can it mock network or service calls?
Yes — specify which APIs or services to mock (e.g., global.fetch, authService) so the generated tests include vi.fn() setups.