tdd-vitest-typescript
Test-Driven Development (TDD) using Vitest and TypeScript. Use when the user requests help with TDD, writing tests before code, test-first development, Vitest test setup, TypeScript testing patterns, unit testing, integration testing, or following the Red-Green-Refactor cycle with Vitest.
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o tdd-vitest-typescript.zip https://jpskill.com/download/19053.zip && unzip -o tdd-vitest-typescript.zip && rm tdd-vitest-typescript.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/19053.zip -OutFile "$d\tdd-vitest-typescript.zip"; Expand-Archive "$d\tdd-vitest-typescript.zip" -DestinationPath $d -Force; ri "$d\tdd-vitest-typescript.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
tdd-vitest-typescript.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
tdd-vitest-typescriptフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 4
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
VitestとTypeScriptによるTDD
VitestとTypeScriptを使用したテスト駆動開発のワークフローについてClaudeをガイドします。
コアTDDサイクル:Red-Green-Refactor
常にこの3段階のサイクルに従ってください。
- Red: 望ましい動作を定義する、失敗するテストを書きます。
- Green: テストがパスするように最小限のコードを書きます。
- Refactor: テストをGreenに保ちながら、コードの品質を向上させます。
ワークフローパターン
// 1. RED: まずテストを書きます
describe('Calculator', () => {
it('adds two numbers', () => {
const calc = new Calculator();
expect(calc.add(2, 3)).toBe(5);
});
});
// テストを実行 → 失敗することを確認 (Red)
// 2. GREEN: 最小限のコードを実装します
class Calculator {
add(a: number, b: number): number {
return a + b;
}
}
// テストを実行 → パスすることを確認 (Green)
// 3. REFACTOR: 必要に応じて、テストをGreenに保ちながら改善します
Vitestのセットアップと設定
基本的なVitestの設定
vitest.config.tsを作成します。
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node', // DOMテストの場合は 'jsdom'
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
});
TypeScriptの設定
tsconfig.jsonに以下が含まれていることを確認してください。
{
"compilerOptions": {
"types": ["vitest/globals"],
"esModuleInterop": true,
"skipLibCheck": true
}
}
テストファイルの構成
命名規則
- テストファイル:
*.test.tsまたは*.spec.ts - テストはソースファイルの隣、または
__tests__ディレクトリに配置します。 - テストファイル名はソースファイルと一致させます:
calculator.ts→calculator.test.ts
構造パターン
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
describe('FeatureName', () => {
// セットアップ
beforeEach(() => {
// 各テストの前に実行されます
});
afterEach(() => {
// 各テストの後にクリーンアップします
});
describe('specific behavior', () => {
it('does something specific', () => {
// Arrange
const input = setupTestData();
// Act
const result = performAction(input);
// Assert
expect(result).toBe(expected);
});
});
});
TypeScriptのテストパターン
型安全なテストデータ
interface User {
id: number;
name: string;
email: string;
}
function createTestUser(overrides?: Partial<User>): User {
return {
id: 1,
name: 'Test User',
email: 'test@example.com',
...overrides,
};
}
it('processes user data', () => {
const user = createTestUser({ name: 'Custom Name' });
expect(processUser(user)).toBeDefined();
});
ジェネリック関数のテスト
describe('generic array utilities', () => {
it('filters array by predicate', () => {
const numbers = [1, 2, 3, 4, 5];
const result = filter(numbers, (n: number) => n > 3);
expect(result).toEqual([4, 5]);
});
});
非同期/Promiseコードのテスト
describe('async operations', () => {
it('fetches user data', async () => {
const user = await fetchUser(1);
expect(user.id).toBe(1);
});
it('handles errors', async () => {
await expect(fetchUser(-1)).rejects.toThrow('Invalid ID');
});
});
モックとスタブ
モジュールモック
import { vi } from 'vitest';
import { fetchData } from './api';
vi.mock('./api');
describe('data processing', () => {
it('processes fetched data', async () => {
vi.mocked(fetchData).mockResolvedValue({ data: 'test' });
const result = await processData();
expect(result).toBe('processed: test');
});
});
関数スパイ
describe('event handling', () => {
it('calls callback on event', () => {
const callback = vi.fn();
const handler = new EventHandler(callback);
handler.trigger('test-event');
expect(callback).toHaveBeenCalledWith('test-event');
expect(callback).toHaveBeenCalledTimes(1);
});
});
部分モック
import * as utils from './utils';
vi.spyOn(utils, 'helperFunction').mockReturnValue('mocked');
it('uses mocked helper', () => {
const result = mainFunction();
expect(utils.helperFunction).toHaveBeenCalled();
expect(result).toContain('mocked');
});
一般的なテストパターン
クラスのテスト
describe('UserService', () => {
let service: UserService;
let mockRepository: MockRepository;
beforeEach(() => {
mockRepository = new MockRepository();
service = new UserService(mockRepository);
});
it('creates user with valid data', async () => {
const userData = { name: 'John', email: 'john@example.com' };
const user = await service.createUser(userData);
expect(user.id).toBeDefined();
expect(mockRepository.save).toHaveBeenCalledWith(
expect.objectContaining(userData)
);
});
});
純粋関数のテスト
describe('pure utility functions', () => {
it('capitalizes first letter', () => {
expect(capitalize('hello')).toBe('Hello');
expect(capitalize('')).toBe('');
expect(capitalize('WORLD')).toBe('WORLD');
});
});
エラーハンドリングのテスト
describe('error scenarios', () => {
it('throws on invalid input', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
it('returns error result', () => {
const result = parseJSON('invalid json');
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
});
パラメータライズドテスト
import { describe, it, expect } from 'vitest';
describe.each([
{ input: 2, expected: 4 },
{ input: 3, expected: 9 },
{ input: 4, expected: 16 },
])('square function', ({ input, expected }) => {
it(`squares ${input} to ${expected}`, () => {
expect(square(input)).toBe(expected);
});
});
テストカバレッジのガイドライン
カバレッジの実行
vitest --coverage
カバレッジの目標
- ビジネスロジックで80%以上のカバレッジを目指します。
- 100%
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
TDD with Vitest and TypeScript
Guide Claude through Test-Driven Development workflows using Vitest and TypeScript.
Core TDD Cycle: Red-Green-Refactor
Always follow this three-phase cycle:
- Red: Write a failing test that defines desired behavior
- Green: Write minimal code to make the test pass
- Refactor: Improve code quality while keeping tests green
Workflow Pattern
// 1. RED: Write the test first
describe('Calculator', () => {
it('adds two numbers', () => {
const calc = new Calculator();
expect(calc.add(2, 3)).toBe(5);
});
});
// Run test → Watch it fail (Red)
// 2. GREEN: Implement minimal code
class Calculator {
add(a: number, b: number): number {
return a + b;
}
}
// Run test → Watch it pass (Green)
// 3. REFACTOR: Improve if needed while keeping tests green
Vitest Setup and Configuration
Basic Vitest Config
Create vitest.config.ts:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node', // or 'jsdom' for DOM testing
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
});
TypeScript Configuration
Ensure tsconfig.json includes:
{
"compilerOptions": {
"types": ["vitest/globals"],
"esModuleInterop": true,
"skipLibCheck": true
}
}
Test File Organization
Naming Conventions
- Test files:
*.test.tsor*.spec.ts - Place tests adjacent to source files or in
__tests__directories - Match test file names to source files:
calculator.ts→calculator.test.ts
Structure Pattern
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
describe('FeatureName', () => {
// Setup
beforeEach(() => {
// Runs before each test
});
afterEach(() => {
// Cleanup after each test
});
describe('specific behavior', () => {
it('does something specific', () => {
// Arrange
const input = setupTestData();
// Act
const result = performAction(input);
// Assert
expect(result).toBe(expected);
});
});
});
TypeScript Testing Patterns
Type-Safe Test Data
interface User {
id: number;
name: string;
email: string;
}
function createTestUser(overrides?: Partial<User>): User {
return {
id: 1,
name: 'Test User',
email: 'test@example.com',
...overrides,
};
}
it('processes user data', () => {
const user = createTestUser({ name: 'Custom Name' });
expect(processUser(user)).toBeDefined();
});
Testing Generic Functions
describe('generic array utilities', () => {
it('filters array by predicate', () => {
const numbers = [1, 2, 3, 4, 5];
const result = filter(numbers, (n: number) => n > 3);
expect(result).toEqual([4, 5]);
});
});
Testing Async/Promise Code
describe('async operations', () => {
it('fetches user data', async () => {
const user = await fetchUser(1);
expect(user.id).toBe(1);
});
it('handles errors', async () => {
await expect(fetchUser(-1)).rejects.toThrow('Invalid ID');
});
});
Mocking and Stubbing
Module Mocks
import { vi } from 'vitest';
import { fetchData } from './api';
vi.mock('./api');
describe('data processing', () => {
it('processes fetched data', async () => {
vi.mocked(fetchData).mockResolvedValue({ data: 'test' });
const result = await processData();
expect(result).toBe('processed: test');
});
});
Function Spies
describe('event handling', () => {
it('calls callback on event', () => {
const callback = vi.fn();
const handler = new EventHandler(callback);
handler.trigger('test-event');
expect(callback).toHaveBeenCalledWith('test-event');
expect(callback).toHaveBeenCalledTimes(1);
});
});
Partial Mocks
import * as utils from './utils';
vi.spyOn(utils, 'helperFunction').mockReturnValue('mocked');
it('uses mocked helper', () => {
const result = mainFunction();
expect(utils.helperFunction).toHaveBeenCalled();
expect(result).toContain('mocked');
});
Common Testing Patterns
Testing Classes
describe('UserService', () => {
let service: UserService;
let mockRepository: MockRepository;
beforeEach(() => {
mockRepository = new MockRepository();
service = new UserService(mockRepository);
});
it('creates user with valid data', async () => {
const userData = { name: 'John', email: 'john@example.com' };
const user = await service.createUser(userData);
expect(user.id).toBeDefined();
expect(mockRepository.save).toHaveBeenCalledWith(
expect.objectContaining(userData)
);
});
});
Testing Pure Functions
describe('pure utility functions', () => {
it('capitalizes first letter', () => {
expect(capitalize('hello')).toBe('Hello');
expect(capitalize('')).toBe('');
expect(capitalize('WORLD')).toBe('WORLD');
});
});
Testing Error Handling
describe('error scenarios', () => {
it('throws on invalid input', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
it('returns error result', () => {
const result = parseJSON('invalid json');
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
});
Parametric Tests
import { describe, it, expect } from 'vitest';
describe.each([
{ input: 2, expected: 4 },
{ input: 3, expected: 9 },
{ input: 4, expected: 16 },
])('square function', ({ input, expected }) => {
it(`squares ${input} to ${expected}`, () => {
expect(square(input)).toBe(expected);
});
});
Test Coverage Guidelines
Running Coverage
vitest --coverage
Coverage Targets
- Aim for 80%+ coverage on business logic
- 100% coverage on critical paths (authentication, payments, etc.)
- Don't obsess over 100% everywhere—focus on meaningful tests
What to Test
Always test:
- Business logic and domain rules
- Error handling and edge cases
- Public APIs and interfaces
- Data transformations
Consider skipping:
- Simple getters/setters
- Framework/library code
- Trivial type definitions
- Configuration files
TDD Best Practices
Write Tests First
Always start with the test, not the implementation:
// ❌ BAD: Writing implementation first
class Calculator {
add(a: number, b: number) { return a + b; }
}
// ✅ GOOD: Test first
it('adds two numbers', () => {
expect(new Calculator().add(2, 3)).toBe(5);
});
One Assertion Per Test
Keep tests focused:
// ❌ BAD: Multiple concerns
it('user operations', () => {
const user = createUser();
expect(user.id).toBeDefined();
expect(updateUser(user)).toBeTruthy();
expect(deleteUser(user.id)).toBeUndefined();
});
// ✅ GOOD: Single concern
it('creates user with ID', () => {
const user = createUser();
expect(user.id).toBeDefined();
});
it('updates existing user', () => {
const user = createUser();
expect(updateUser(user)).toBeTruthy();
});
Test Behavior, Not Implementation
// ❌ BAD: Testing implementation details
it('calls internal helper method', () => {
const service = new Service();
const spy = vi.spyOn(service as any, '_internalHelper');
service.process();
expect(spy).toHaveBeenCalled();
});
// ✅ GOOD: Testing behavior
it('processes data correctly', () => {
const service = new Service();
const result = service.process(inputData);
expect(result).toEqual(expectedOutput);
});
Keep Tests Fast
- Use mocks for external dependencies (databases, APIs, file system)
- Avoid sleep/setTimeout in tests
- Run expensive setup once with beforeAll when safe
Descriptive Test Names
// ❌ BAD: Vague
it('works', () => { /* ... */ });
// ✅ GOOD: Descriptive
it('returns empty array when no users match filter criteria', () => {
/* ... */
});
Common TDD Workflow
Starting a New Feature
- Write a high-level test describing the feature:
describe('User Registration', () => {
it('creates new user account with valid email', async () => {
const result = await registerUser({
email: 'new@example.com',
password: 'secure123',
});
expect(result.success).toBe(true);
expect(result.user.email).toBe('new@example.com');
});
});
- Run test → See it fail (Red)
- Implement minimal code → See it pass (Green)
- Add edge case tests:
it('rejects registration with existing email', async () => {
await registerUser({ email: 'existing@example.com', password: 'pass' });
const result = await registerUser({
email: 'existing@example.com',
password: 'pass2',
});
expect(result.success).toBe(false);
expect(result.error).toContain('Email already registered');
});
it('rejects weak passwords', async () => {
const result = await registerUser({
email: 'new@example.com',
password: '123',
});
expect(result.success).toBe(false);
expect(result.error).toContain('Password too weak');
});
- Refactor implementation while keeping tests green
Debugging Failed Tests
When tests fail unexpectedly:
- Check test isolation—are tests interfering with each other?
- Verify mocks are properly reset between tests
- Use
it.only()to run single test - Add console.log or debugger statements
- Check async timing issues
Integration Testing with Vitest
Testing Multiple Units Together
describe('Order Processing Integration', () => {
let database: TestDatabase;
let paymentGateway: MockPaymentGateway;
let orderService: OrderService;
beforeEach(async () => {
database = await TestDatabase.create();
paymentGateway = new MockPaymentGateway();
orderService = new OrderService(database, paymentGateway);
});
afterEach(async () => {
await database.cleanup();
});
it('completes order flow from cart to confirmation', async () => {
const user = await database.createUser();
const cart = await orderService.createCart(user.id);
await orderService.addItem(cart.id, { productId: 1, quantity: 2 });
paymentGateway.simulateSuccess();
const order = await orderService.checkout(cart.id);
expect(order.status).toBe('confirmed');
expect(order.items).toHaveLength(1);
});
});
Watch Mode
Vitest runs in watch mode by default during development:
vitest
This automatically re-runs tests when files change, enabling rapid TDD cycles.
Quick Reference: Common Matchers
// Equality
expect(value).toBe(5); // Strict equality (===)
expect(value).toEqual({ a: 1 }); // Deep equality
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
// Numbers
expect(value).toBeGreaterThan(3);
expect(value).toBeLessThan(10);
expect(value).toBeCloseTo(0.3); // Floating point
// Strings
expect(string).toMatch(/pattern/);
expect(string).toContain('substring');
// Arrays
expect(array).toContain(item);
expect(array).toHaveLength(3);
// Objects
expect(object).toHaveProperty('key');
expect(object).toMatchObject({ a: 1 }); // Partial match
// Exceptions
expect(() => fn()).toThrow();
expect(() => fn()).toThrow('Error message');
expect(async () => fn()).rejects.toThrow();
// Functions
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledWith(arg1, arg2);
expect(fn).toHaveBeenCalledTimes(2);
Tips for Effective TDD
- Start simple: Begin with the simplest test case, not the most complex
- Take small steps: Write one test, make it pass, refactor, repeat
- Trust the process: Resist urge to write implementation before tests
- Refactor fearlessly: With good test coverage, refactoring is safe
- Keep tests maintainable: Tests are code too—keep them clean and DRY
- Run tests frequently: Vitest's watch mode makes this effortless
- Write tests for bugs: When you find a bug, write a test that exposes it first
When to Use This Skill
Apply TDD when:
- Building new features from scratch
- Fixing bugs (write failing test first)
- Refactoring existing code
- Learning a new API or library
- Working on critical business logic
TDD is especially valuable for:
- Pure functions and algorithms
- Business logic and domain models
- Data transformations
- API endpoints and services
同梱ファイル
※ ZIPに含まれるファイル一覧。`SKILL.md` 本体に加え、参考資料・サンプル・スクリプトが入っている場合があります。
- 📄 SKILL.md (13,036 bytes)
- 📎 references/advanced-patterns.md (13,453 bytes)
- 📎 references/anti-patterns.md (12,880 bytes)
- 📎 scripts/setup-vitest.js (5,562 bytes)