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

4.1 KiB

name, description
name description
dev-unit-test Vue 3 컴포넌트를 받아 Vitest + Vue Test Utils 기반의 단위 테스트를 자동 생성합니다. Props, Emits, 슬롯, 사용자 인터랙션, 비동기 동작을 모두 커버합니다. 다음 상황에서 반드시 사용하세요: - "이 컴포넌트 단위 테스트 작성해줘", "테스트 코드 만들어줘" - "Vitest 어떻게 써?", "Vue Test Utils 사용 방법" - 컴포넌트 완성 후 커버리지를 확보해야 할 때

단위 테스트 생성 (dev-unit-test)

.vue 컴포넌트 → Vitest + Vue Test Utils 단위 테스트 자동 생성.

언제 사용하는가

  • 컴포넌트 개발 완료 후 테스트 코드를 작성할 때
  • TDD 방식으로 테스트를 먼저 작성할 때
  • 프로젝트 테스트 커버리지 목표를 달성해야 할 때

입력

  • 테스트할 .vue 파일 경로
  • 테스트 케이스 범위 (없으면 자동 도출)

작업 순서

Phase 1: 컴포넌트 분석

  1. 대상 .vue 파일을 읽어 아래 항목을 파악한다.

    • Props 목록 (타입, 기본값, 필수 여부)
    • Emits 목록 (이벤트 이름, 페이로드 타입)
    • 슬롯 유무
    • 외부 의존성 (composables, store, $fetch)
    • 인터랙션 (버튼 클릭, 입력, 폼 제출)
    • 비동기 동작 (API 호출, 로딩 상태)
    • 조건부 렌더링 (v-if, v-show)
  2. 테스트 케이스 목록을 도출한다.

Phase 2: 테스트 설정

// 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

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 검증

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 검증

it('버튼 클릭 시 click 이벤트를 emit한다', async () => {
  const wrapper = mount(ComponentName, { props: { id: '1' } })
  await wrapper.find('button').trigger('click')
  expect(wrapper.emitted('click')?.[0]).toEqual(['1'])
})

조건부 렌더링

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

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()
})

슬롯

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  # 커버리지 확인
\`\`\`