Skip to main content

testing-frontend

Use when writing component tests, testing user interactions, mocking APIs, or setting up Vitest/React Testing Library/Vue Test Utils for frontend applications.

소스 정보

저장소
MadAppGang/claude-code
최근 소스 활동
2026년 1월 20일 08:16
감지된 SKILL.md 언어
영어
스타
287
포크
24

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
testing-frontend
version
1.0.0
description
Use when writing component tests, testing user interactions, mocking APIs, or setting up Vitest/React Testing Library/Vue Test Utils for frontend applications.
keywords
["frontend testing","Vitest","React Testing Library","Vue Test Utils","component testing","user event testing","mocking","accessibility testing"]
plugin
dev
updated
2026-01-20T00:00:00.000Z
# Frontend Testing Patterns ## Overview Testing patterns for frontend applications using Vitest and React Testing Library / Vue Test Utils. ## Testing Philosophy ### User-Centric Testing Test behavior, not implementation. Query elements the way users would find them. ```tsx // BAD: Testing implementation expect(wrapper.state('isOpen')).toBe(true); expect(wrapper.find('.modal-class').exists()).toBe(true); // GOOD: Testing behavior expect(screen.getByRole('dialog')).toBeInTheDocument(); expect(screen.getByText('Modal Title')).toBeVisible(); ``` ### Query Priority Use queries in this order (most to least preferred): 1. `getByRole` - Accessible to everyone 2. `getByLabelText` - Form elements 3. `getByPlaceholderText` - Inputs 4. `getByText` - Non-interactive elements 5. `getByDisplayValue` - Form current values 6. `getByAltText` - Images 7. `getByTestId` - Last resort ## Component Testing (React) ### Basic Component Test ```tsx import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { UserCard } from './UserCard'; describe('UserCard', () => { const user = { id: '1', name: 'John Doe', email: 'john@example.com' }; it('renders user information', () => { render(<UserCard user={user} />); expect(screen.getByText('John Doe')).toBeInTheDocument(); expect(screen.getByText('john@example.com')).toBeInTheDocument(); }); it('calls onSelect when clicked', async () => { const onSelect = vi.fn(); const userEvt = userEvent.setup(); render(<UserCard user={user} onSelect={onSelect} />); await userEvt.click(screen.getByRole('button')); expect(onSelect).toHaveBeenCalledWith(user); }); }); ``` ### Testing Async Components ```tsx import { render, screen, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { UserList } from './UserList'; // Mock API vi.mock('@/api', () => ({ getUsers: vi.fn(), })); describe('UserList', () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }); const wrapper = ({ children }) => ( <QueryClientProvider client={queryClient}> {children} </QueryClientProvider> ); beforeEach(() => { queryClient.clear(); }); it('shows loading state', () => { api.getUsers.mockImplementation(() => new Promise(() => {})); render(<UserList />, { wrapper }); expect(screen.getByText('Loading...')).toBeInTheDocument(); }); it('shows users when loaded', async () => { api.getUsers.mockResolvedValue([ { id: '1', name: 'John' }, { id: '2', name: 'Jane' }, ]); render(<UserList />, { wrapper }); await waitFor(() => { expect(screen.getByText('John')).toBeInTheDocument(); expect(screen.getByText('Jane')).toBeInTheDocument(); }); }); it('shows error message on failure', async () => { api.getUsers.mockRejectedValue(new Error('Network error')); render(<UserList />, { wrapper }); await waitFor(() => { expect(screen.getByText(/error/i)).toBeInTheDocument(); }); }); }); ``` ### Testing Forms ```tsx import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { LoginForm } from './LoginForm'; describe('LoginForm', () => { it('submits form with valid data', async () => { const onSubmit = vi.fn(); const user = userEvent.setup(); render(<LoginForm onSubmit={onSubmit} />); await user.type(screen.getByLabelText(/email/i), 'test@example.com'); await user.type(screen.getByLabelText(/password/i), 'password123'); await user.click(screen.getByRole('button', { name: /submit/i })); expect(onSubmit).toHaveBeenCalledWith({ email: 'test@example.com', password: 'password123', }); }); it('shows validation errors', async () => { const user = userEvent.setup(); render(<LoginForm onSubmit={vi.fn()} />); // Submit empty form await user.click(screen.getByRole('button', { name: /submit/i })); expect(screen.getByText(/email is required/i)).toBeInTheDocument(); expect(screen.getByText(/password is required/i)).toBeInTheDocument(); }); it('disables submit button while submitting', async () => { const user = userEvent.setup(); render( <LoginForm onSubmit={() => new Promise((resolve) => setTimeout(resolve, 100))} /> ); await user.type(screen.getByLabelText(/email/i), 'test@example.com'); await user.type(screen.getByLabelText(/password/i), 'password123'); await user.click(screen.getByRole('button', { name: /submit/i })); expect(screen.getByRole('button', { name: /submitting/i })).toBeDisabled(); }); }); ``` ## Component Testing (Vue) ### Basic Component Test ```ts import { mount } from '@vue/test-utils'; import UserCard from './UserCard.vue'; describe('UserCard', () => { const user = { id: '1', name: 'John Doe', email: 'john@example.com' }; it('renders user information', () => { const wrapper = mount(UserCard, { props: { user }, }); expect(wrapper.text()).toContain('John Doe'); expect(wrapper.text()).toContain('john@example.com'); }); it('emits select event when clicked', async () => { const wrapper = mount(UserCard, { props: { user }, }); await wrapper.find('button').trigger('click'); expect(wrapper.emitted('select')).toBeTruthy(); expect(wrapper.emitted('select')[0]).toEqual([user]); }); }); ``` ### Testing with Pinia ```ts import { mount } from '@vue/test-utils'; import { createTestingPinia } from '@pinia/testing'; import UserList from './UserList.vue'; import { useUserStore } from '@/stores/userStore'; describe('UserList', () => { it('renders users from store', () => { const wrapper = mount(UserList, { global: { plugins: [ createTestingPinia({ initialState: { user: { users: [ { id: '1', name: 'John' }, { id: '2', name: 'Jane' }, ], }, }, }), ], }, }); expect(wrapper.text()).toContain('John'); expect(wrapper.text()).toContain('Jane'); }); it('calls fetchUsers on mount', () => { mount(UserList, { global: { plugins: [createTestingPinia()], }, }); const store = useUserStore(); expect(store.fetchUsers).toHaveBeenCalled(); }); }); ``` ## Hook Testing ```tsx import { renderHook, act, waitFor } from '@testing-library/react'; import { useCounter } from './useCounter'; describe('useCounter', () => { it('initializes with default value', () => { const { result } = renderHook(() => useCounter()); expect(result.current.count).toBe(0); }); it('initializes with custom value', () => { const { result } = renderHook(() => useCounter({ initial: 10 })); expect(result.current.count).toBe(10); }); it('increments counter', () => { const { result } = renderHook(() => useCounter()); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); }); it('respects max limit', () => { const { result } = renderHook(() => useCounter({ initial: 9, max: 10 })); act(() => { result.current.increment(); result.current.increment(); }); expect(result.current.count).toBe(10); }); }); ``` ## Mocking ### API Mocking ```tsx // __mocks__/api.ts export const api = { getUsers: vi.fn(), createUser: vi.fn(), updateUser: vi.fn(), deleteUser: vi.fn(), }; // In test vi.mock('@/api'); beforeEach(() => { vi.clearAllMocks(); }); it('fetches users', async () => { api.getUsers.mockResolvedValue([{ id: '1', name: 'John' }]); render(<UserList />); await waitFor(() => { expect(api.getUsers).toHaveBeenCalledTimes(1); }); }); ``` ### Module Mocking ```tsx // Mock entire module vi.mock('@/utils/date', () => ({ formatDate: vi.fn(() => '2024-01-01'), })); // Mock specific export vi.mock('@/config', async () => { const actual = await vi.importActual('@/config'); return { ...actual, API_URL: 'http://test-api.com', }; }); ``` ### Timer Mocking ```tsx describe('Debounced search', () => { beforeEach(() => { vi.useFakeTimers(); });
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기