Vitest
GitHub概要
vitest-dev/vitest
Next generation testing framework powered by Vite.
スター14,667
ウォッチ54
フォーク1,385
作成日:2021年12月3日
言語:TypeScript
ライセンス:MIT License
トピックス
testtesting-toolsvite
スター履歴
データ取得日時: 2025/7/20 02:18
テストツール
Vitest
概要
VitestはViteを基盤とした次世代のテストフレームワークです。Viteのトランスフォームとモジュール解決を活用し、非常に高速で軽量なテスト実行を実現します。TypeScriptのネイティブサポートと、Jest互換のAPIを提供することで、既存プロジェクトからの移行を容易にしています。開発時のHMR(Hot Module Replacement)に似た体験で、テストの変更が即座に反映され、開発効率を大幅に向上させます。
詳細
Vitestは現代のフロントエンド開発に最適化されたテストフレームワークとして設計されており、Viteプロジェクトとの親和性が非常に高いのが特徴です。ESMファーストのアプローチを取り、ネイティブESモジュールのサポートにより高速な実行が可能です。内蔵のコードカバレッジレポート機能、並行テスト実行、ウォッチモードでのスマートリテスト機能を提供します。
モッキング機能ではvi.mock()を使用し、動的インポートやモジュールのスタブ化が簡単に行えます。ブラウザAPIのモッキングにはhappy-domやjsdomが利用可能で、DOM操作のテストも効率的に実行できます。また、スナップショットテスト、非同期テスト、カスタムマッチャーの作成なども標準でサポートしています。
メリット・デメリット
メリット
- 高速実行: Viteのトランスフォームエンジンにより、Jestの最大10倍高速
- ゼロコンフィグ: TypeScript、JSX、ESMの設定が不要
- Jest互換API: 既存のJestテストが最小限の変更で移行可能
- 並行実行: デフォルトでワーカースレッドを使用した並行テスト実行
- コードカバレッジ: c8またはistanbulによる内蔵カバレッジレポート
- ウォッチモード: ファイル変更の検出とスマートリテスト機能
デメリット
- 新しいツール: Jestと比べて情報やプラグインが少ない
- ブラウザサポート: 実ブラウザでのテストには追加設定が必要
- 学習コスト: Viteに慣れていない場合の導入負荷
- デバッグ環境: 一部IDEでのデバッグサポートが限定的
参考ページ
書き方の例
Hello World
// vitest.config.js
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'node'
}
})
// hello.test.js
import { describe, it, expect } from 'vitest'
describe('Hello World', () => {
it('should return greeting message', () => {
function greet(name) {
return `Hello, ${name}!`
}
expect(greet('Vitest')).toBe('Hello, Vitest!')
})
})
基本テスト
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
describe('Calculator', () => {
let calculator
beforeEach(() => {
calculator = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b
}
})
it('should add numbers correctly', () => {
expect(calculator.add(2, 3)).toBe(5)
expect(calculator.add(-1, 1)).toBe(0)
})
it('should handle edge cases', () => {
expect(calculator.add(0.1, 0.2)).toBeCloseTo(0.3)
expect(calculator.multiply(5, 0)).toBe(0)
})
it('should validate input types', () => {
expect(() => calculator.add('a', 2)).toThrow()
})
})
モック
import { describe, it, expect, vi, beforeEach } from 'vitest'
describe('API Client', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should mock fetch calls', async () => {
const mockFetch = vi.fn().mockResolvedValue({
json: () => Promise.resolve({ data: 'test data' })
})
global.fetch = mockFetch
const response = await fetch('/api/data')
const data = await response.json()
expect(mockFetch).toHaveBeenCalledWith('/api/data')
expect(data).toEqual({ data: 'test data' })
})
it('should spy on module methods', () => {
const consoleSpy = vi.spyOn(console, 'log')
console.log('test message')
expect(consoleSpy).toHaveBeenCalledWith('test message')
consoleSpy.mockRestore()
})
})
非同期テスト
import { describe, it, expect } from 'vitest'
describe('Async Operations', () => {
it('should handle promises', async () => {
const asyncFunction = () => {
return new Promise(resolve => {
setTimeout(() => resolve('async result'), 100)
})
}
const result = await asyncFunction()
expect(result).toBe('async result')
})
it('should test promise rejections', async () => {
const failingFunction = () => {
return Promise.reject(new Error('Operation failed'))
}
await expect(failingFunction()).rejects.toThrow('Operation failed')
})
it('should handle async/await with timeout', async () => {
const slowFunction = () => {
return new Promise(resolve => {
setTimeout(() => resolve('slow result'), 50)
})
}
await expect(slowFunction()).resolves.toBe('slow result')
}, 1000) // 1秒のタイムアウト
})
セットアップ・ティアダウン
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest'
describe('Database Operations', () => {
beforeAll(async () => {
// テストスイート開始前の一回だけ実行
await setupDatabase()
})
afterAll(async () => {
// テストスイート終了後の一回だけ実行
await cleanupDatabase()
})
beforeEach(async () => {
// 各テスト前に実行
await seedTestData()
})
afterEach(async () => {
// 各テスト後に実行
await clearTestData()
})
it('should create user', async () => {
const user = await createUser({ name: 'Test User' })
expect(user.id).toBeDefined()
expect(user.name).toBe('Test User')
})
it('should update user', async () => {
const user = await createUser({ name: 'Original Name' })
const updatedUser = await updateUser(user.id, { name: 'Updated Name' })
expect(updatedUser.name).toBe('Updated Name')
})
})
高度な機能
import { describe, it, expect, vi } from 'vitest'
describe('Advanced Features', () => {
it('should support custom matchers', () => {
expect.extend({
toBeWithinRange(received, floor, ceiling) {
const pass = received >= floor && received <= ceiling
if (pass) {
return {
message: () => `expected ${received} not to be within range ${floor} - ${ceiling}`,
pass: true,
}
} else {
return {
message: () => `expected ${received} to be within range ${floor} - ${ceiling}`,
pass: false,
}
}
},
})
expect(15).toBeWithinRange(10, 20)
})
it('should test with snapshots', () => {
const data = {
id: 123,
name: 'Test User',
createdAt: '2024-01-01T00:00:00Z'
}
expect(data).toMatchSnapshot()
})
it('should support parametrized tests', ({ value, expected }) => {
expect(Math.abs(value)).toBe(expected)
}, [
{ value: -5, expected: 5 },
{ value: 10, expected: 10 },
{ value: -15, expected: 15 }
])
it('should mock timers', async () => {
vi.useFakeTimers()
const callback = vi.fn()
setTimeout(callback, 1000)
expect(callback).not.toHaveBeenCalled()
vi.advanceTimersByTime(1000)
expect(callback).toHaveBeenCalled()
vi.useRealTimers()
})
})