mcpField
J
jest-typescript

jest-typescript

github.com/bobmatnycテスト・品質78354 回インストール15 週間前に更新
リポジトリ

jest-typescript について

jest-typescriptは、TypeScriptプロジェクトでJestを設定するためのスキルです。テストパターン、Reactテスト、モック、スナップショット、カバレッジを扱います。

主な機能

  1. 01ts-jestによるJest設定
  2. 02型安全なテストの作成
  3. 03モジュール、メソッド、タイマーのモック
  4. 04Reactコンポーネントとフックのテスト
  5. 05スナップショットとカバレッジレポート
  6. 06Vitestからのテスト移行

活用シーン

  1. 01TypeScriptプロジェクトでのJest設定
  2. 02Testing Libraryを使ったReactコンポーネントのテスト
  3. 03カバレッジ測定またはVitestからの移行

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill jest-typescript

インストール後、Claude Code で/jest-typescriptを入力して読み込まれたか確認します。

同梱ファイルmetadata.json

詳細

Jest + TypeScript - 業界標準のテスト

概要

Jest は、70%の市場シェアを持つ業界標準のテストフレームワークであり、TypeScript プロジェクト向けに成熟した実績のあるエコシステムを提供します。スナップショットテスト、モック、カバレッジレポートを標準搭載し、包括的なテスト機能を備えています。

主な機能:

  • 🏆 業界標準:市場シェア70%で、広く採用されています
  • 📦 オールインワン:テストランナー、アサーション、モック、カバレッジを1つのパッケージに集約
  • 📸 スナップショットテスト:UI テスト向けのスナップショット機能を標準搭載
  • 🧪 React との連携:React Testing Library、enzyme との互換性
  • 🔧 成熟したエコシステム:豊富なプラグイン、ツール、コミュニティサポート
  • 🎯 TypeScript のサポート:ts-jest による完全な型安全性
  • 🔍 カバレッジレポート:Istanbul のカバレッジ機能を標準搭載
  • 🌐 マルチプラットフォーム:Node.js、ブラウザー(jsdom)、React Native

インストール:

npm install -D jest @types/jest ts-jest
npm install -D @testing-library/react @testing-library/jest-dom  # For React

基本設定

1. Jest の設定を初期化する
npx ts-jest config:init

これにより jest.config.js が作成されます:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};
2. 手動で設定する

jest.config.ts(TypeScript の設定):

import type { Config } from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.test.{ts,tsx}',
    '!src/**/__tests__/**',
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
};

export default config;
3. TypeScript の設定

tsconfig.json:

{
  "compilerOptions": {
    "types": ["jest", "@testing-library/jest-dom"],
    "esModuleInterop": true
  }
}

tsconfig.test.json(テスト専用):

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "types": ["jest", "node", "@testing-library/jest-dom"]
  },
  "include": ["src/**/*.test.ts", "src/**/*.spec.ts", "src/**/__tests__/**"]
}
4. Package.json のスクリプト
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:ci": "jest --ci --coverage --maxWorkers=2"
  }
}

基本的なテストパターン

基本的なテスト構造
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';

describe('Calculator', () => {
  let calculator: Calculator;

  beforeEach(() => {
    calculator = new Calculator();
  });

  afterEach(() => {
    // Cleanup
  });

  it('adds two numbers correctly', () => {
    const result = calculator.add(2, 3);
    expect(result).toBe(5);
  });

  it('handles negative numbers', () => {
    expect(calculator.add(-5, 3)).toBe(-2);
  });

  it.each([
    [1, 1, 2],
    [2, 3, 5],
    [10, -5, 5],
  ])('adds %i + %i to equal %i', (a, b, expected) => {
    expect(calculator.add(a, b)).toBe(expected);
  });
});
型安全な TypeScript テスト
interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

describe('User Service', () => {
  it('creates user with correct types', () => {
    const user: User = {
      id: 1,
      name: 'Alice',
      email: '[email protected]',
      role: 'admin',
    };

    // Type-safe assertions
    expect(user.id).toEqual(expect.any(Number));
    expect(user.name).toEqual(expect.any(String));
    expect(user.role).toMatch(/^(admin|user)$/);
  });

  it('validates user object shape', () => {
    const user = createUser('Bob', '[email protected]');

    expect(user).toMatchObject({
      id: expect.any(Number),
      name: 'Bob',
      email: '[email protected]',
    });
  });
});

TypeScript でのモック

モジュールのモックに jest.mock を使う
import { jest } from '@jest/globals';
import { UserService } from './UserService';
import * as userApi from './api/userApi';

// Mock entire module
jest.mock('./api/userApi');

describe('UserService with Mocks', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('fetches user data', async () => {
    const mockUser = { id: 1, name: 'Alice', email: '[email protected]' };

    // Type-safe mock
    const mockedFetchUser = jest.mocked(userApi.fetchUser);
    mockedFetchUser.mockResolvedValue(mockUser);

    const service = new UserService();
    const user = await service.getUser(1);

    expect(mockedFetchUser).toHaveBeenCalledWith(1);
    expect(user).toEqual(mockUser);
  });
});
メソッドのスパイに jest.spyOn を使う
import { jest } from '@jest/globals';

class Logger {
  log(message: string): void {
    console.log(message);
  }

  error(message: string): void {
    console.error(message);
  }
}

describe('Logger Spy', () => {
  let logger: Logger;
  let logSpy: jest.SpyInstance;

  beforeEach(() => {
    logger = new Logger();
    logSpy = jest.spyOn(logger, 'log');
  });

  afterEach(() => {
    logSpy.mockRestore();
  });

  it('tracks method calls', () => {
    logger.log('Hello');
    logger.log('World');

    expect(logSpy).toHaveBeenCalledTimes(2);
    expect(logSpy).toHaveBeenCalledWith('Hello');
    expect(logSpy).toHaveBeenLastCalledWith('World');
  });

  it('provides custom implementation', () => {
    logSpy.mockImplementation((msg: string) => {
      console.log(`[CUSTOM] ${msg}`);
    });

    logger.log('Test');
    expect(logSpy).toHaveBeenCalledWith('Test');
  });
});
型安全なモック関数
import { jest } from '@jest/globals';

interface ApiResponse<T> {
  data: T;
  status: number;
}

type FetchUserFn = (id: number) => Promise<ApiResponse<User>>;

describe('Type-Safe Mocks', () => {
  it('creates typed mock function', async () => {
    const mockFetchUser = jest.fn<FetchUserFn>()
      .mockResolvedValue({
        data: { id: 1, name: 'Alice', email: '[email protected]', role: 'user' },
        status: 200,
      });

    const result = await mockFetchUser(1);

    expect(result.data.name).toBe('Alice');
    expect(result.status).toBe(200);
    expect(mockFetchUser).toHaveBeenCalledWith(1);
  });

  it('uses mock implementation', () => {
    const mockCalculate = jest.fn<(x: number, y: number) => number>()
      .mockImplementation((x, y) => x + y);

    expect(mockCalculate(5, 3)).toBe(8);
    expect(mockCalculate).toHaveBeenCalledWith(5, 3);
  });
});
タイマーのモック
import { jest } from '@jest/globals';

describe('Timer Mocking', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  it('fast-forwards time', () => {
    const callback = jest.fn();
    setTimeout(callback, 1000);

    jest.advanceTimersByTime(500);
    expect(callback).not.toHaveBeenCalled();

    jest.advanceTimersByTime(500);
    expect(callback).toHaveBeenCalledTimes(1);
  });

  it('runs all timers', () => {
    const callback = jest.fn();
    setTimeout(callback, 1000);
    setTimeout(callback, 2000);

    jest.runAllTimers();
    expect(callback).toHaveBeenCalledTimes(2);
  });

  it('handles intervals', () => {
    const callback = jest.fn();
    setInterval(callback, 1000);

    jest.advanceTimersByTime(3500);
    expect(callback).toHaveBeenCalledTimes(3);

    jest.clearAllTimers();
  });
});

React Testing Library + TypeScript

React のセットアップ
npm install -D @testing-library/react @testing-library/jest-dom @testing-library/user-event
npm install -D jest-environment-jsdom

jest.config.ts(React):

import type { Config } from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/test/setup.ts'],
  moduleNameMapper: {
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
    '\\.(jpg|jpeg|png|gif|svg)$': '<rootDir>/__mocks__/fileMock.js',
  },
  transform: {
    '^.+\\.tsx?$': ['ts-jest', {
      tsconfig: {
        jsx: 'react-jsx',
      },
    }],
  },
};

export default config;

src/test/setup.ts:

import '@testing-library/jest-dom';
import { cleanup } from '@testing-library/react';
import { afterEach } from '@jest/globals';

afterEach(() => {
  cleanup();
});
React コンポーネントのテスト
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Counter } from './Counter';

describe('Counter Component', () => {
  it('renders initial count', () => {
    render(<Counter initialCount={0} />);
    expect(screen.getByText('Count: 0')).toBeInTheDocument();
  });

  it('increments counter on button click', async () => {
    const user = userEvent.setup();
    render(<Counter initialCount={0} />);

    const button = screen.getByRole('button', { name: /increment/i });
    await user.click(button);

    expect(screen.getByText('Count: 1')).toBeInTheDocument();
  });

  it('calls onChange callback with correct value', async () => {
    const onChange = jest.fn();
    const user = userEvent.setup();

    render(<Counter initialCount={5} onChange={onChange} />);

    await user.click(screen.getByRole('button', { name: /increment/i }));

    expect(onChange).toHaveBeenCalledWith(6);
    expect(onChange).toHaveBeenCalledTimes(1);
  });

  it('disables button when max count reached', () => {
    render(<Counter initialCount={10} maxCount={10} />);

    const button = screen.getByRole('button', { name: /increment/i });
    expect(button).toBeDisabled();
  });
});
フックのテスト
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';

describe('useCounter Hook', () => {
  it('initializes with default value', () => {
    const { result } = renderHook(() => useCounter(0));
    expect(result.current.count).toBe(0);
  });

  it('increments counter', () => {
    const { result } = renderHook(() => useCounter(0));

    act(() => {
      result.current.increment();
    });

    expect(result.current.count).toBe(1);
  });

  it('decrements counter', () => {
    const { result } = renderHook(() => useCounter(5));

    act(() => {
      result.current.decrement();
    });

    expect(result.current.count).toBe(4);
  });

  it('resets to initial value', () => {
    const { result } = renderHook(() => useCounter(10));

    act(() => {
      result.current.increment();
      result.current.increment();
    });

    expect(result.current.count).toBe(12);

    act(() => {
      result.current.reset();
    });

    expect(result.current.count).toBe(10);
  });
});
非同期コンポーネントのテスト
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserProfile } from './UserProfile';
import * as api from './api';

jest.mock('./api');

describe('UserProfile Async', () => {
  it('loads and displays user data', async () => {
    const mockUser = { id: 1, name: 'Alice', email: '[email protected]' };
    jest.mocked(api.fetchUser).mockResolvedValue(mockUser);

    render(<UserProfile userId={1} />);

    expect(screen.getByText('Loading...')).toBeInTheDocument();

    await waitFor(() => {
      expect(screen.getByText('Alice')).toBeInTheDocument();
    });

    expect(screen.getByText('[email protected]')).toBeInTheDocument();
  });

  it('displays error on fetch failure', async () => {
    jest.mocked(api.fetchUser).mockRejectedValue(new Error('Network error'));

    render(<UserProfile userId={1} />);

    await waitFor(() => {
      expect(screen.getByText(/error/i)).toBeInTheDocument();
    });
  });
});

スナップショットテスト

コンポーネントのスナップショット
import { render } from '@testing-library/react';
import { UserCard } from './UserCard';

describe('UserCard Snapshots', () => {
  it('matches snapshot for regular user', () => {
    const { container } = render(
      <UserCard
        name="Alice"
        email="[email protected]"
        role="user"
      />
    );

    expect(container.firstChild).toMatchSnapshot();
  });

  it('matches snapshot for admin user', () => {
    const { container } = render(
      <UserCard
        name="Bob"
        email="[email protected]"
        role="admin"
      />
    );

    expect(container.firstChild).toMatchSnapshot();
  });

  it('uses inline snapshot', () => {
    const user = { id: 1, name: 'Charlie', role: 'user' };

    expect(user).toMatchInlineSnapshot(`
      {
        "id": 1,
        "name": "Charlie",
        "role": "user",
      }
    `);
  });
});
スナップショットの更新
# Update all snapshots
jest --updateSnapshot
jest -u

# Update snapshots for specific test file
jest UserCard.test.tsx -u

# Interactive snapshot update
jest --watch
# Press 'u' to update failing snapshots
カスタムスナップショットシリアライザー
// __tests__/serializers/dateSerializer.ts
export default {
  test: (val: any) => val instanceof Date,
  print: (val: Date) => `Date(${val.toISOString()})`,
};

jest.config.ts:

const config: Config = {
  snapshotSerializers: ['<rootDir>/__tests__/serializers/dateSerializer.ts'],
};

非同期テスト

Promise のテスト
import { fetchData, saveData } from './api';

describe('Async Operations', () => {
  it('resolves with data', async () => {
    const data = await fetchData(1);
    expect(data).toBeDefined();
    expect(data.id).toBe(1);
  });

  it('handles promise rejection', async () => {
    await expect(fetchData(-1)).rejects.toThrow('Invalid ID');
  });

  it('uses resolves matcher', async () => {
    await expect(fetchData(1)).resolves.toHaveProperty('id', 1);
  });

  it('tests multiple async operations', async () => {
    const [user, posts] = await Promise.all([
      fetchUser(1),
      fetchPosts(1),
    ]);

    expect(user.id).toBe(1);
    expect(posts).toHaveLength(expect.any(Number));
  });
});
コールバックのテスト
describe('Callback Testing', () => {
  it('calls callback with correct arguments', (done) => {
    function fetchWithCallback(id: number, callback: (data: any) => void) {
      setTimeout(() => {
        callback({ id, name: 'Test' });
      }, 100);
    }

    fetchWithCallback(1, (data) => {
      try {
        expect(data.id).toBe(1);
        expect(data.name).toBe('Test');
        done();
      } catch (error) {
        done(error);
      }
    });
  });
});

カバレッジの設定

高度なカバレッジ設定

jest.config.ts:

const config: Config = {
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coverageProvider: 'v8', // or 'babel' for compatibility
  coverageReporters: ['text', 'lcov', 'html', 'json'],
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.test.{ts,tsx}',
    '!src/**/__tests__/**',
    '!src/index.ts',
    '!src/types/**',
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
    './src/core/': {
      branches: 90,
      functions: 90,
      lines: 90,
      statements: 90,
    },
  },
  coveragePathIgnorePatterns: [
    '/node_modules/',
    '/dist/',
    '/__tests__/',
  ],
};
カバレッジの実行
# Generate coverage report
npm test -- --coverage

# Coverage with watch mode
npm test -- --coverage --watch

# Coverage for specific files
npm test -- --coverage --collectCoverageFrom="src/components/**/*.tsx"

# View HTML report
open coverage/lcov-report/index.html

Vitest からの移行

主な違い

API の変更点:

// Vitest
import { vi } from 'vitest';
const mockFn = vi.fn();
vi.spyOn(obj, 'method');

// Jest
import { jest } from '@jest/globals';
const mockFn = jest.fn();
jest.spyOn(obj, 'method');
移行チェックリスト

1. 依存関係を更新する:

npm uninstall vitest @vitest/ui
npm install -D jest @types/jest ts-jest

2. package.json を更新する:

{
  "scripts": {
    "test": "jest",           // Was: vitest run
    "test:watch": "jest --watch"  // Was: vitest
  }
}

3. vitest.config.ts を jest.config.ts に置き換える:

// Old: vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
  },
});

// New: jest.config.ts
import type { Config } from 'jest';
const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  globals: {
    'ts-jest': {
      isolatedModules: true,
    },
  },
};
export default config;

4. テストファイルを更新する:

// Change imports
- import { vi } from 'vitest';
+ import { jest } from '@jest/globals';

// Update mocks
- vi.fn()
+ jest.fn()

- vi.spyOn()
+ jest.spyOn()

- vi.mock()
+ jest.mock()

// Timer mocks
- vi.useFakeTimers()
+ jest.useFakeTimers()

- vi.advanceTimersByTime()
+ jest.advanceTimersByTime()

5. tsconfig.json を更新する:

{
  "compilerOptions": {
    "types": ["jest", "@testing-library/jest-dom"]  // Was: vitest/globals
  }
}

Jest と Vitest の比較

パフォーマンス

Jest:

  • 初回起動が遅い(HMR なし)
  • デフォルトではテストを逐次実行
  • 中規模プロジェクトでは1~5秒

Vitest:

  • HMR ベースで即座に実行
  • デフォルトでは並列実行
  • 同じプロジェクトでは100~500ミリ秒
エコシステム

Jest:

  • ✅ 市場シェア70%
  • ✅ 成熟したエコシステム(8年以上)
  • ✅ Stack Overflow の回答がより豊富
  • ✅ 企業からのサポートが充実

Vitest:

  • ✅ モダンで、採用が広がっています
  • ✅ Vite とのネイティブ連携
  • ⚠️ エコシステムは比較的小規模
  • ⚠️ 利用できるリソースが少なめ
TypeScript のサポート

Jest:

  • ts-jest の設定が必要
  • 追加の変換ステップが必要
  • コンパイルが遅い

Vitest:

  • TypeScript を標準サポート
  • 設定は不要
  • Vite によって高速化
Jest を使う場合

次の場合は Jest を選択します:

  • ✅ すでに Jest を使用している既存プロジェクト
  • ✅ 実績のあるツールが求められる企業環境
  • ✅ 豊富なエコシステムのサポートが必要なプロジェクト
  • ✅ Create React App を使用する React プロジェクト
  • ✅ Vite 以外のビルドシステム(Webpack、Rollup)

次の場合は Vitest を選択します:

  • ✅ モダンなツールを使う新規プロジェクト
  • ✅ Vite ベースのアプリケーション
  • ✅ パフォーマンスが重要なテストスイート
  • ✅ ESM を優先するプロジェクト

ベストプラクティス

  1. TypeScript の設定を使用する:型安全なテストにより、実行時エラーを防げます
  2. 外部依存関係をモックする:ネットワーク、ファイルシステム、データベース
  3. テストを分離する:各テストは独立している必要があります
  4. describe ブロックを使用する:関連するテストを論理的にグループ化します
  5. モックの状態をクリアする:beforeEach で jest.clearAllMocks() を使用します
  6. エッジケースをテストする:空の配列、null、undefined、エラー
  7. データ駆動テストには .each を使用する:複数の入力を効率よくテストできます
  8. 実装のテストを避ける:内部構造ではなく、動作をテストします
  9. テストを高速に保つ:低速な処理をモックし、並列実行を使用します
  10. カバレッジのしきい値を維持する:CI で最低カバレッジを必須にします

よくある落とし穴

❌ テスト間でモックをクリアしない:

// WRONG - mocks leak between tests
it('test 1', () => {
  jest.spyOn(api, 'fetch');
  // No cleanup!
});

// CORRECT
afterEach(() => {
  jest.restoreAllMocks();
});

❌ 非同期テストで await を忘れる:

// WRONG - test completes before assertion
it('fetches data', () => {
  fetchData().then(data => {
    expect(data).toBeDefined();  // Never runs!
  });
});

// CORRECT
it('fetches data', async () => {
  const data = await fetchData();
  expect(data).toBeDefined();
});

❌ 誤ったテスト環境を使用する:

// WRONG - testing DOM without jsdom
// jest.config.ts
testEnvironment: 'node',  // Can't test React!

// CORRECT
testEnvironment: 'jsdom',

❌ モックに TypeScript の型を使用しない:

// WRONG - no type safety
const mockFn = jest.fn();

// CORRECT
const mockFn = jest.fn<(id: number) => Promise<User>>();

リソース

関連スキル

Jest を使用する際は、次の補完的なスキルも検討してください。

  • typescript-core:高度な TypeScript パターン、tsconfig の最適化、型安全性
  • react:Testing Library を使用した React コンポーネントのテストパターン
  • vitest:Vite ネイティブのパフォーマンスと高速な実行を備えた最新の代替手段
TypeScript の型安全性に関するクイックリファレンス(単独利用向けに埋め込み)
// Type-safe test helpers with generics
function createMockUser<T extends Partial<User>>(overrides: T): User & T {
  return {
    id: 1,
    name: 'Test User',
    email: '[email protected]',
    ...overrides
  };
}

// Usage with type inference
const adminUser = createMockUser({ role: 'admin' });
// Type: User & { role: string }

// Type-safe mock functions
const mockFetch = jest.fn<typeof fetch>();
mockFetch.mockResolvedValue(new Response('{}'));

// Const type parameters for literal types
const createConfig = <const T extends Record<string, unknown>>(config: T): T => config;
const testConfig = createConfig({ environment: 'test', debug: true });
// Type: { environment: "test"; debug: true } (literals preserved)
React テストパターンのクイックリファレンス(単独利用向けに埋め込み)
// React Testing Library with Jest
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';

// Component testing pattern
describe('UserProfile', () => {
  it('should display user information', () => {
    const user = { id: 1, name: 'Alice', email: '[email protected]' };
    render(<UserProfile user={user} />);

    expect(screen.getByText('Alice')).toBeInTheDocument();
    expect(screen.getByText('[email protected]')).toBeInTheDocument();
  });

  it('should handle user interactions', async () => {
    const onSubmit = jest.fn();
    render(<UserForm onSubmit={onSubmit} />);

    // User interactions
    await userEvent.type(screen.getByLabelText('Name'), 'Bob');
    await userEvent.click(screen.getByRole('button', { name: 'Submit' }));

    await waitFor(() => {
      expect(onSubmit).toHaveBeenCalledWith({ name: 'Bob' });
    });
  });
});

// Hook testing
import { renderHook, act } from '@testing-library/react';

test('useCounter hook', () => {
  const { result } = renderHook(() => useCounter(0));

  expect(result.current.count).toBe(0);

  act(() => {
    result.current.increment();
  });

  expect(result.current.count).toBe(1);
});

// Context and Provider testing
const wrapper = ({ children }: { children: React.ReactNode }) => (
  <AuthProvider>{children}</AuthProvider>
);

test('useAuth hook with context', () => {
  const { result } = renderHook(() => useAuth(), { wrapper });
  expect(result.current.user).toBeDefined();
});
Vitest のクイック比較(単独利用向けに埋め込み)

Jest より Vitest を選ぶ場合:

  • 新しい Vite/Vite ベースのプロジェクト(Turbopack を使用する Next.js、SvelteKit)
  • テスト実行の高速化が必要な場合(10~100 倍高速)
  • ESM を優先するアーキテクチャ
  • テストでのホットモジュールリプレースメント

Jest を使い続ける場合:

  • Jest がすでに設定されている既存の大規模コードベース
  • Jest のワークフローが確立されている企業環境
  • 成熟したエコシステムと豊富なプラグインが必要な場合
  • Create React App を使用する React アプリ(デフォルトの Jest 設定)

移行スニペット(Jest → Vitest):

// Jest: import from '@testing-library/jest-dom'
import '@testing-library/jest-dom';

// Vitest: import from vitest globals
import { expect, test, describe } from 'vitest';
import { screen } from '@testing-library/react';

// Most Jest syntax works in Vitest unchanged
test('component renders', () => {
  render(<Component />);
  expect(screen.getByText('Hello')).toBeTruthy();
});

[TypeScript、React、Vitest のパターン全文は、それぞれのスキルを一緒に導入した場合に利用できます]

まとめ

  • Jest は市場シェア 70% を占める業界標準です
  • TypeScript サポート:ts-jest による完全な型安全性
  • オールインワンのソリューション:テストランナー、アサーション、モック、カバレッジ
  • React Testing Library との連携によるコンポーネントテスト
  • 成熟したエコシステムと豊富なツール、サポート
  • スナップショットテストによる UI のリグレッションテスト
  • 互換性のある API による Vitest からの移行パス
  • 最適な用途:既存のプロジェクト、企業環境、React アプリ、レガシーサポート
  • トレードオフ:Vitest より低速ですが、より成熟しており、広くサポートされています

コメント