jpskill.com
🛠️ 開発・MCP コミュニティ

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.

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
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
🪟 Windows (PowerShell)
$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. 1. 下の青いボタンを押して tdd-vitest-typescript.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → tdd-vitest-typescript フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
4

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

VitestとTypeScriptによるTDD

VitestとTypeScriptを使用したテスト駆動開発のワークフローについてClaudeをガイドします。

コアTDDサイクル:Red-Green-Refactor

常にこの3段階のサイクルに従ってください。

  1. Red: 望ましい動作を定義する、失敗するテストを書きます。
  2. Green: テストがパスするように最小限のコードを書きます。
  3. 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.tscalculator.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:

  1. Red: Write a failing test that defines desired behavior
  2. Green: Write minimal code to make the test pass
  3. 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.ts or *.spec.ts
  • Place tests adjacent to source files or in __tests__ directories
  • Match test file names to source files: calculator.tscalculator.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

  1. 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');
  });
});
  1. Run test → See it fail (Red)
  2. Implement minimal code → See it pass (Green)
  3. 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');
});
  1. Refactor implementation while keeping tests green

Debugging Failed Tests

When tests fail unexpectedly:

  1. Check test isolation—are tests interfering with each other?
  2. Verify mocks are properly reset between tests
  3. Use it.only() to run single test
  4. Add console.log or debugger statements
  5. 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

  1. Start simple: Begin with the simplest test case, not the most complex
  2. Take small steps: Write one test, make it pass, refactor, repeat
  3. Trust the process: Resist urge to write implementation before tests
  4. Refactor fearlessly: With good test coverage, refactoring is safe
  5. Keep tests maintainable: Tests are code too—keep them clean and DRY
  6. Run tests frequently: Vitest's watch mode makes this effortless
  7. 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` 本体に加え、参考資料・サンプル・スクリプトが入っている場合があります。