프론트엔드(blog-frontend)에 TDD(Test-Driven Development) 방식으로 기능을 구현합니다. 새로운 기능 개발, 버그 수정, 리팩토링 시 사용합니다.
$ARGUMENTS 기능을 TDD 방식으로 구현합니다.
모든 작업은 blog-frontend/ 디렉토리에서 수행합니다.
반드시 아래 순서를 따라야 합니다:
*.test.tsx 파일cd blog-frontend && npm test -- --run
테스트 없이 프로덕션 코드 작성 금지
한 번에 하나의 테스트만
테스트 실행 필수
작은 단위로 진행
// src/components/Button/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, 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/i })).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
// src/hooks/useCounter.test.ts
import { renderHook, act } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('should initialize with default value', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
it('should increment counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});
// src/features/posts/PostList.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { PostList } from './PostList';
describe('PostList', () => {
it('displays loading state initially', () => {
render(<PostList />);
expect(screen.getByText(/loading/i)).toBeInTheDocument();
});
it('displays posts after fetch', async () => {
render(<PostList />);
await waitFor(() => {
expect(screen.getByText('Post Title')).toBeInTheDocument();
});
});
});
React Testing Library의 쿼리 우선순위를 따릅니다:
// Good - 역할 기반 쿼리
screen.getByRole('button', { name: /submit/i });
screen.getByRole('textbox', { name: /email/i });
// Avoid - testId는 최후의 수단
screen.getByTestId('submit-button');
테스트 파일은 테스트 대상과 같은 폴더에 배치합니다:
src/components/Button/
├── index.ts
├── Button.tsx
├── Button.test.tsx # 테스트 파일
└── Button.module.css
<style jsx> 문법 미지원Next.js에서 <style jsx> 문법은 기본 지원되지 않습니다. CSS 애니메이션은 globals.css에 정의하고 Tailwind arbitrary values를 사용하세요:
/* globals.css */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
// Tailwind arbitrary values로 사용
<div className="animate-[fadeIn_0.6s_ease-out]">...</div>
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
describe("postApi", () => {
const mockFetch = vi.fn();
beforeEach(() => {
vi.stubGlobal("fetch", mockFetch);
});
afterEach(() => {
vi.unstubAllGlobals();
});
it("API 호출 테스트", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ id: 1, title: "제목" }),
});
const result = await createPost({ title: "제목", content: "내용", authorId: 1 });
expect(mockFetch).toHaveBeenCalledWith("/api/posts", expect.objectContaining({
method: "POST",
}));
});
});
각 TDD 사이클마다 다음을 보고합니다: