--- name: dev-unit-test description: | Vue 3 컴포넌트를 받아 Vitest + Vue Test Utils 기반의 단위 테스트를 자동 생성합니다. Props, Emits, 슬롯, 사용자 인터랙션, 비동기 동작을 모두 커버합니다. 다음 상황에서 반드시 사용하세요: - "이 컴포넌트 단위 테스트 작성해줘", "테스트 코드 만들어줘" - "Vitest 어떻게 써?", "Vue Test Utils 사용 방법" - 컴포넌트 완성 후 커버리지를 확보해야 할 때 --- # 단위 테스트 생성 (dev-unit-test) `.vue` 컴포넌트 → Vitest + Vue Test Utils 단위 테스트 자동 생성. ## 언제 사용하는가 - 컴포넌트 개발 완료 후 테스트 코드를 작성할 때 - TDD 방식으로 테스트를 먼저 작성할 때 - 팀 테스트 커버리지 기준(80%↑)을 달성해야 할 때 ## 입력 - 테스트할 `.vue` 파일 경로 - 테스트 케이스 범위 (없으면 자동 도출) --- ## 작업 순서 ### Phase 1: 컴포넌트 분석 1. 대상 `.vue` 파일을 읽어 아래 항목을 파악한다. - Props 목록 (타입, 기본값, 필수 여부) - Emits 목록 (이벤트 이름, 페이로드 타입) - 슬롯 유무 - 외부 의존성 (composables, store, $fetch) - 인터랙션 (버튼 클릭, 입력, 폼 제출) - 비동기 동작 (API 호출, 로딩 상태) - 조건부 렌더링 (`v-if`, `v-show`) 2. 테스트 케이스 목록을 도출한다. ### Phase 2: 테스트 설정 ```ts // vitest.config.ts (없으면 생성 안내) import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], test: { environment: 'jsdom', globals: true, }, }) ``` ### Phase 3: 테스트 파일 생성 파일 위치: 컴포넌트와 동일 디렉토리 또는 `__tests__/` 폴더. 파일명: `.spec.ts` ```ts import { describe, it, expect, vi } from 'vitest' import { mount } from '@vue/test-utils' import ComponentName from './ComponentName.vue' describe('ComponentName', () => { // Phase 4 케이스 작성 }) ``` ### Phase 4: 테스트 케이스 작성 패턴 #### Props 검증 ```ts it('title prop을 렌더링한다', () => { const wrapper = mount(ComponentName, { props: { title: '테스트 제목' } }) expect(wrapper.find('h2').text()).toBe('테스트 제목') }) it('필수 prop 누락 시 기본값을 사용한다', () => { const wrapper = mount(ComponentName) expect(wrapper.find('[data-testid="label"]').text()).toBe('기본값') }) ``` #### Emits 검증 ```ts it('버튼 클릭 시 click 이벤트를 emit한다', async () => { const wrapper = mount(ComponentName, { props: { id: '1' } }) await wrapper.find('button').trigger('click') expect(wrapper.emitted('click')?.[0]).toEqual(['1']) }) ``` #### 조건부 렌더링 ```ts it('isLoading이 true이면 스피너를 표시한다', () => { const wrapper = mount(ComponentName, { props: { isLoading: true } }) expect(wrapper.find('[aria-busy="true"]').exists()).toBe(true) expect(wrapper.find('[data-testid="content"]').exists()).toBe(false) }) ``` #### 비동기 / 스토어 Mock ```ts import { createTestingPinia } from '@pinia/testing' it('마운트 시 fetchProducts를 호출한다', async () => { const wrapper = mount(ComponentName, { global: { plugins: [createTestingPinia({ createSpy: vi.fn })] } }) const store = useProductStore() expect(store.fetchProducts).toHaveBeenCalledOnce() }) ``` #### 슬롯 ```ts it('default 슬롯 콘텐츠를 렌더링한다', () => { const wrapper = mount(ComponentName, { slots: { default: '슬롯 내용' } }) expect(wrapper.find('span').text()).toBe('슬롯 내용') }) ``` --- ## 출력 형식 ``` ## 단위 테스트: .spec.ts ### 테스트 케이스 목록 1. [렌더링] 기본 렌더링 성공 2. [Props] title prop 표시 3. [Emits] 버튼 클릭 시 emit 4. [조건부] isLoading 상태 처리 ... ### 코드 \`\`\`ts [전체 spec 파일] \`\`\` ### 실행 방법 \`\`\`bash npx vitest run <파일경로> npx vitest --coverage # 커버리지 확인 \`\`\` ```