Python: pytest
설치 및 설정
터미널
# 의존성 설치
pip install pytest pytest-asyncio httpx
# pytest.ini (설정 파일)
[pytest]
testpaths = tests
asyncio_mode = autoAPI 테스트 예제
tests/test_projects.py
# tests/test_projects.py
import pytest
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_create_project():
"""프로젝트 생성 테스트"""
response = client.post(
"/api/v1/projects",
json={"name": "Test Project", "description": "A test"}
)
assert response.status_code == 201
data = response.json()
assert data["name"] == "Test Project"
assert "id" in data
def test_create_project_validation():
"""이름 필드 검증 테스트"""
response = client.post("/api/v1/projects", json={})
assert response.status_code == 422 # Validation error
def test_get_projects_empty():
"""빈 목록 반환 테스트"""
response = client.get("/api/v1/projects")
assert response.status_code == 200
assert response.json()["items"] == []테스트 실행
터미널
# 전체 테스트 실행
pytest
# 상세 출력
pytest -v
# 특정 파일만
pytest tests/test_projects.py
# 특정 함수만
pytest tests/test_projects.py::test_create_project
# 커버리지 확인
pip install pytest-cov
pytest --cov=app --cov-report=htmlFastAPI TestClient
FastAPI의 TestClient는
실제 HTTP 요청 없이 API를 테스트할 수 있습니다.
비동기 테스트가 필요하면 httpx.AsyncClient를 사용하세요.
Node.js: Vitest
설치 및 설정
터미널
# 의존성 설치
npm install -D vitest
# package.json scripts
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
}
}vitest.config.ts
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: false,
environment: 'node', // 또는 'jsdom' (브라우저 환경)
},
});Hono API 테스트 예제
src/index.test.ts
// src/index.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { app } from './index';
describe('Todos API', () => {
// 각 테스트 전에 저장소 초기화
beforeEach(() => {
// Reset storage
});
describe('POST /api/v1/todos', () => {
it('should create a todo', async () => {
const res = await app.request('/api/v1/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Test Todo' }),
});
expect(res.status).toBe(201);
const data = await res.json();
expect(data.title).toBe('Test Todo');
expect(data.is_completed).toBe(false);
});
it('should return 422 for empty title', async () => {
const res = await app.request('/api/v1/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: '' }),
});
expect(res.status).toBe(422); // Validation error
});
});
describe('PATCH /api/v1/todos/:id', () => {
it('should toggle todo completion', async () => {
// Create a todo first
const createRes = await app.request('/api/v1/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Test' }),
});
const { id } = await createRes.json();
// Toggle completion
const res = await app.request(`/api/v1/todos/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ is_completed: true }),
});
expect(res.status).toBe(200);
const data = await res.json();
expect(data.is_completed).toBe(true);
});
});
});Hono의 app.request()
Hono는 app.request() 메서드로
실제 서버 없이 HTTP 요청을 시뮬레이션할 수 있습니다.
Vue: Vitest + @vue/test-utils
설정
설정
# 의존성 설치
npm install -D vitest @vue/test-utils jsdom
# vitest.config.ts
import { defineConfig } from 'vitest/config';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom', // 브라우저 환경 시뮬레이션
globals: false,
},
});Composable 테스트 예제
src/__tests__/useTodos.test.ts
// src/__tests__/useTodos.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { flushPromises } from '@vue/test-utils';
import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query';
import { createApp, h, defineComponent } from 'vue';
import { useTodos, useCreateTodo } from '../composables/useTodos';
// Vue composable 테스트를 위한 헬퍼
function withSetup<T>(composable: () => T) {
let result!: T;
const TestComponent = defineComponent({
setup() {
result = composable();
return () => h('div');
},
});
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});
const app = createApp(TestComponent);
app.use(VueQueryPlugin, { queryClient });
app.mount(document.createElement('div'));
return { result, app };
}
describe('useTodos', () => {
beforeEach(() => {
vi.restoreAllMocks();
});
it('should fetch todos list', async () => {
const mockResponse = {
items: [{ id: '1', title: 'Todo 1', is_completed: false }],
total: 1,
};
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(mockResponse),
});
const { result, app } = withSetup(() => useTodos());
await flushPromises();
await new Promise(resolve => setTimeout(resolve, 100));
expect(result.data.value).toEqual(mockResponse);
app.unmount();
});
});jsdom 환경 필수
Vue 컴포넌트와 composable 테스트에는 environment: 'jsdom'이 필요합니다.
설정하지 않으면 document is not defined 에러가 발생합니다.
React: Vitest + @testing-library/react
설정
터미널
# 의존성 설치
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdomHook 테스트 예제
src/__tests__/useProjects.test.ts
// src/__tests__/useProjects.test.ts
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useProjects, useCreateProject } from '../hooks/useProjects';
const createWrapper = () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
},
});
return ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
};
describe('useProjects', () => {
it('should fetch projects', async () => {
const mockData = { items: [{ id: '1', name: 'Test' }], total: 1 };
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(mockData),
});
const { result } = renderHook(() => useProjects(), {
wrapper: createWrapper(),
});
await waitFor(() => {
expect(result.current.data).toEqual(mockData);
});
});
});모킹 (Mocking)
외부 의존성(API, 데이터베이스)을 모킹하여 격리된 테스트를 작성합니다.
Vitest에서 fetch 모킹
모킹 예제
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
// 전역 fetch 모킹
const originalFetch = global.fetch;
beforeEach(() => {
global.fetch = vi.fn();
});
afterEach(() => {
global.fetch = originalFetch;
});
it('should handle API response', async () => {
// 성공 응답 모킹
vi.mocked(global.fetch).mockResolvedValue({
ok: true,
json: () => Promise.resolve({ data: 'test' }),
} as Response);
// 테스트 실행
const result = await fetchData();
expect(result.data).toBe('test');
});
it('should handle API error', async () => {
// 에러 응답 모킹
vi.mocked(global.fetch).mockResolvedValue({
ok: false,
status: 500,
} as Response);
// 에러 처리 검증
await expect(fetchData()).rejects.toThrow();
});베스트 프랙티스
DO
- ✅ 하나의 테스트에 하나의 검증
- ✅ 테스트 이름에 의도 명시
- ✅ 외부 의존성 모킹
- ✅ 엣지 케이스 테스트
- ✅ 에러 상황 테스트
DON'T
- ❌ 테스트 간 상태 공유
- ❌ 실제 API/DB 호출
- ❌ sleep/setTimeout 의존
- ❌ 구현 세부사항 테스트
- ❌ 매직 넘버 사용
다음 단계
단위 테스트를 마스터했다면, E2E 테스트 가이드에서 Playwright로 전체 사용자 시나리오를 테스트하는 방법을 배웁니다.