Files
game-fe-agent/skills/dev-unit-test/SKILL.md

161 lines
4.1 KiB
Markdown

---
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__/` 폴더.
파일명: `<ComponentName>.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: '<span>슬롯 내용</span>' }
})
expect(wrapper.find('span').text()).toBe('슬롯 내용')
})
```
---
## 출력 형식
```
## 단위 테스트: <ComponentName>.spec.ts
### 테스트 케이스 목록
1. [렌더링] 기본 렌더링 성공
2. [Props] title prop 표시
3. [Emits] 버튼 클릭 시 emit
4. [조건부] isLoading 상태 처리
...
### 코드
\`\`\`ts
[전체 spec 파일]
\`\`\`
### 실행 방법
\`\`\`bash
npx vitest run <파일경로>
npx vitest --coverage # 커버리지 확인
\`\`\`
```