Composable テスト
20.3.1. Composable 単体テスト
Composable は Vue コンポーネントコンテキスト内で実行されなければなりません。ラッパー関数を使用します。
typescript
// test-utils/withSetup.ts
import { createApp, type App } from 'vue'
export function withSetup<T>(composable: () => T): [T, App] {
let result!: T
const app = createApp({
setup() { result = composable(); return () => {} },
})
app.mount(document.createElement('div'))
return [result, app]
}typescript
describe('useCounter', () => {
it('increment 呼び出し時に count が増加しなければなりません', () => {
const [result] = withSetup(() => useCounter())
result.increment()
expect(result.count.value).toBe(1)
})
})- Vue コンテキストに依存しない純粋関数はラッパーなしで直接テストします。
20.3.2. 外部依存関係のモッキング
API、Router などの外部依存関係は vi.mock() でモッキングします。
typescript
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { withSetup } from '@/test-utils/withSetup'
import { useUserList } from '../useUserList'
import { fetchUsers } from '@/api/user'
vi.mock('@/api/user', () => ({ fetchUsers: vi.fn() }))
vi.mock('vue-router', () => ({
useRouter: vi.fn(() => ({ push: vi.fn() })),
useRoute: vi.fn(() => ({ params: {}, query: {} })),
}))
describe('useUserList', () => {
beforeEach(() => { vi.clearAllMocks() })
it('ユーザー一覧を取得しなければなりません', async () => {
vi.mocked(fetchUsers).mockResolvedValue([{ id: 1 }])
const [result] = withSetup(() => useUserList())
await result.load()
expect(result.users.value).toHaveLength(1)
})
})beforeEachでvi.clearAllMocks()を使用してテスト間の状態を初期化します。
20.3.3. 非同期 Composable
非同期ロジックは flushPromises で Promise 解決後に状態を検証します。
typescript
import { describe, it, expect, vi } from 'vitest'
import { flushPromises } from '@vue/test-utils'
import { withSetup } from '@/test-utils/withSetup'
import { useFetchData } from '../useFetchData'
import { fetchItems } from '@/api/item'
vi.mock('@/api/item', () => ({ fetchItems: vi.fn() }))
describe('useFetchData', () => {
it('データを正常に設定しなければなりません', async () => {
vi.mocked(fetchItems).mockResolvedValue([{ id: 1 }])
const [result] = withSetup(() => useFetchData())
await flushPromises()
expect(result.loading.value).toBe(false)
})
it('失敗時にエラーを設定しなければなりません', async () => {
vi.mocked(fetchItems).mockRejectedValue(new Error('エラー'))
const [result] = withSetup(() => useFetchData())
await flushPromises()
expect(result.error.value).toBe('エラー')
})
})20.3.4. タイマーテスト
debounce、throttle などは vi.useFakeTimers() を使用します。
typescript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { withSetup } from '@/test-utils/withSetup'
import { useDebouncedSearch } from '../useDebouncedSearch'
describe('useDebouncedSearch', () => {
beforeEach(() => { vi.useFakeTimers() })
afterEach(() => { vi.useRealTimers() })
it('300ms 後に検索を実行しなければなりません', () => {
const [result] = withSetup(() => useDebouncedSearch())
const spy = vi.spyOn(result, 'executeSearch')
result.updateQuery('テスト')
vi.advanceTimersByTime(300)
expect(spy).toHaveBeenCalledOnce()
})
})beforeEachで有効化し、afterEachでvi.useRealTimers()により復元します。