T-CREATOR

Jest と ESLint を併用するベストプラクティス

Jest と ESLint を併用するベストプラクティス

テストコードの品質向上は、プロジェクトの継続的な成功において欠かせない要素です。Jest でテストを書くだけでなく、ESLint と組み合わせることで、より一貫性があり保守性の高いテストコードを実現できます。

本記事では、Jest と ESLint を効果的に連携させるための具体的な設定方法と、開発現場で役立つベストプラクティスを詳しく解説いたします。テストコードの品質向上に悩んでいる方や、チーム開発でのコード統一に課題を感じている方に、実践的な解決策をお届けします。

Jest と ESLint 併用の重要性

テストコード品質の課題

多くの開発プロジェクトでは、プロダクションコードには厳格なコーディング規約を適用している一方で、テストコードは「動けば良い」という扱いになりがちです。しかし、テストコードもプロジェクトの重要な資産であり、以下のような課題を抱えることがあります。

  • 一貫性のないテスト記述パターン
  • 非効率なアサーション方法
  • テストの可読性低下
  • メンテナンスコストの増大

ESLint 連携がもたらすメリット

Jest と ESLint を適切に連携させることで、これらの課題を解決し、以下のような恩恵を受けられます。

#メリット詳細
1コードの一貫性チーム全体で統一されたテスト記述スタイル
2早期エラー検出実行前に潜在的な問題を発見
3開発効率向上IDE での自動修正とリアルタイム警告
4学習効果ベストプラクティスの自動提案
5保守性向上長期的なコードメンテナンスが容易

Jest 固有の問題への対処

Jest には独特の書き方や慣習があり、一般的な ESLint ルールだけでは以下の問題を適切に検出できません。

typescript// 問題のあるテストコード例
describe('User API', () => {
  // 空のテストケース(eslint-plugin-jestで検出可能)
  it('should create user', () => {});

  // 非推奨なアサーション(eslint-plugin-jestで検出可能)
  it('should validate email', () => {
    expect(validateEmail('test@example.com')).toBe(true);
    expect(validateEmail('invalid')).not.toBe(true); // toBe(false)が推奨
  });

  // フォーカステストの残留(eslint-plugin-jestで検出可能)
  fit('should process payment', () => {
    // テストロジック
  });
});

これらの問題を効果的に検出・修正するために、専用の ESLint プラグインが必要になります。

eslint-plugin-jest の導入手順

パッケージのインストール

まず、プロジェクトに必要なパッケージをインストールします。Yarn を使用して以下のコマンドを実行してください。

bash# 基本パッケージのインストール
yarn add -D eslint-plugin-jest

# TypeScript を使用している場合は追加で
yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser

基本的な ESLint 設定

.eslintrc.js ファイルに Jest プラグインを追加します。既存の設定がある場合は、以下の内容をマージしてください。

javascript// .eslintrc.js
module.exports = {
  // 基本設定
  env: {
    browser: true,
    es2021: true,
    node: true,
    jest: true, // Jest 環境を有効化
  },

  // プラグインの追加
  plugins: [
    'jest',
    // 他のプラグインがある場合はここに追加
  ],

  // 拡張設定の適用
  extends: [
    'eslint:recommended',
    'plugin:jest/recommended', // Jest 推奨ルールを適用
    'plugin:jest/style', // Jest スタイルルールを適用
  ],

  // パーサー設定(TypeScript の場合)
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module',
  },

  // ルール設定
  rules: {
    // Jest 固有のルールはここで調整
  },
};

TypeScript プロジェクトでの設定

TypeScript を使用している場合は、より詳細な設定が必要です。

javascript// .eslintrc.js(TypeScript 対応版)
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
    jest: true,
  },

  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
    'plugin:jest/recommended',
    'plugin:jest/style',
  ],

  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module',
    project: './tsconfig.json', // TypeScript 設定ファイルを指定
  },

  plugins: ['@typescript-eslint', 'jest'],

  // テストファイルに特化した設定
  overrides: [
    {
      files: [
        '**/__tests__/**/*',
        '**/*.{test,spec}.{js,ts,tsx}',
      ],
      env: {
        jest: true,
      },
      extends: ['plugin:jest/recommended'],
    },
  ],
};

設定の動作確認

設定が正しく適用されているかを確認するため、簡単なテストファイルで ESLint を実行してみましょう。

typescript// example.test.ts
describe('設定確認用テスト', () => {
  // 空のテストケース(警告が表示されるはず)
  it('should work', () => {});

  // フォーカステスト(警告が表示されるはず)
  fit('focused test', () => {
    expect(true).toBe(true);
  });
});

以下のコマンドで ESLint を実行し、適切に警告が表示されることを確認してください。

bash# 特定ファイルの検証
yarn eslint example.test.ts

# プロジェクト全体のテストファイルを検証
yarn eslint "**/*.{test,spec}.{js,ts,tsx}"

必須設定ルール一覧

重要度別ルール分類

Jest 用の ESLint ルールを重要度別に分類し、プロジェクトの状況に応じて段階的に導入できるようにします。

#重要度ルール名説明
1必須jest​/​no-disabled-testsスキップされたテストの検出
2必須jest​/​no-focused-testsフォーカステストの検出
3必須jest​/​valid-expectexpect の正しい使用
4推奨jest​/​expect-expectアサーションのないテスト検出
5推奨jest​/​no-identical-title重複するテストタイトル検出

必須ルールの詳細設定

javascript// .eslintrc.js(必須ルール部分)
module.exports = {
  rules: {
    // === 必須ルール ===

    // スキップされたテストを検出(本番環境では危険)
    'jest/no-disabled-tests': 'error',

    // フォーカステストを検出(他のテストが実行されない)
    'jest/no-focused-tests': 'error',

    // expect の不正な使用を検出
    'jest/valid-expect': 'error',

    // 非同期テストでの適切な戻り値を強制
    'jest/valid-expect-in-promise': 'error',

    // describe ブロックの正しい使用を強制
    'jest/valid-describe-callback': 'error',

    // === 推奨ルール ===

    // アサーションのないテストを検出
    'jest/expect-expect': [
      'warn',
      {
        assertFunctionNames: [
          'expect',
          'request.**.expect', // supertest との連携
          'expectSaga', // redux-saga-test-plan との連携
        ],
      },
    ],

    // 重複するテストタイトルを検出
    'jest/no-identical-title': 'warn',

    // 条件付きテストの使用を制限
    'jest/no-conditional-expect': 'warn',
  },
};

スタイルルールの設定

コードの一貫性を保つためのスタイルルールも重要です。

javascript// スタイル関連のルール設定
rules: {
  // === 命名・記述スタイル ===

  // 小文字のテスト名を強制
  'jest/lowercase-name': [
    'warn',
    {
      ignore: ['describe'],
    },
  ],

  // 一貫したテストメソッドの使用(it vs test)
  'jest/consistent-test-it': [
    'warn',
    {
      fn: 'it',
      withinDescribe: 'it',
    },
  ],

  // === アサーションスタイル ===

  // より具体的なマッチャーの使用を推奨
  'jest/prefer-to-be': 'warn',
  'jest/prefer-to-contain': 'warn',
  'jest/prefer-to-have-length': 'warn',

  // 真偽値の適切なアサーション
  'jest/prefer-to-be-falsy': 'warn',
  'jest/prefer-to-be-truthy': 'warn',

  // === 構造・組織 ===

  // テストファイルあたりの最大テスト数
  'jest/max-nested-describe': ['warn', { max: 5 }],

  // 実行前後処理の適切な使用
  'jest/require-top-level-describe': 'warn',
}

プロジェクト別推奨設定

React プロジェクトでの設定

React アプリケーションでは、コンポーネントテストに特化した設定が効果的です。

javascript// .eslintrc.js(React プロジェクト用)
module.exports = {
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:jest/recommended',
    'plugin:jest/style',
    'plugin:testing-library/react', // React Testing Library用
  ],

  plugins: ['react', 'jest', 'testing-library'],

  env: {
    browser: true,
    jest: true,
  },

  overrides: [
    {
      // React コンポーネントテスト専用設定
      files: ['**/*.{test,spec}.{jsx,tsx}'],
      rules: {
        // Testing Library のベストプラクティス
        'testing-library/await-async-query': 'error',
        'testing-library/no-wait-for-empty-callback':
          'error',

        // Jest + React 特有のルール
        'jest/prefer-expect-assertions': 'off', // React テストでは不要な場合が多い
        'jest/expect-expect': [
          'warn',
          {
            assertFunctionNames: [
              'expect',
              'screen.getBy*',
              'screen.findBy*',
            ],
          },
        ],
      },
    },
  ],
};

Next.js プロジェクトでの設定

Next.js では、ページコンポーネントや API ルートのテストに特化した設定が必要です。

javascript// .eslintrc.js(Next.js プロジェクト用)
module.exports = {
  extends: [
    'next/core-web-vitals',
    'plugin:jest/recommended',
    'plugin:jest/style',
  ],

  plugins: ['jest'],

  env: {
    browser: true,
    node: true,
    jest: true,
  },

  overrides: [
    {
      // API ルートテスト用設定
      files: ['**/__tests__/api/**/*.{test,spec}.{js,ts}'],
      env: {
        node: true,
        jest: true,
      },
      rules: {
        // Node.js 環境でのテスト特有の設定
        'jest/expect-expect': [
          'warn',
          {
            assertFunctionNames: [
              'expect',
              'request.**.expect', // supertest 使用時
            ],
          },
        ],
      },
    },
    {
      // ページコンポーネントテスト用設定
      files: [
        '**/__tests__/pages/**/*.{test,spec}.{jsx,tsx}',
      ],
      rules: {
        // Next.js ページコンポーネント特有の設定
        'jest/prefer-expect-assertions': 'off',
      },
    },
  ],
};

Node.js API プロジェクトでの設定

バックエンド API の開発では、非同期処理とデータベース操作のテストが中心となります。

javascript// .eslintrc.js(Node.js API プロジェクト用)
module.exports = {
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
    'plugin:jest/recommended',
    'plugin:jest/style',
  ],

  plugins: ['@typescript-eslint', 'jest'],

  env: {
    node: true,
    jest: true,
  },

  rules: {
    // API テスト特有の設定
    'jest/expect-expect': [
      'warn',
      {
        assertFunctionNames: [
          'expect',
          'request.**.expect', // supertest
          'expectAsync', // 非同期テスト用
          'expectDatabase', // DB テスト用カスタム関数
        ],
      },
    ],

    // 非同期テストの適切な処理を強制
    'jest/no-done-callback': 'warn',
    'jest/valid-expect-in-promise': 'error',

    // テストの実行時間制限
    'jest/no-test-prefixes': 'warn',
  },

  overrides: [
    {
      // 統合テスト用設定
      files: [
        '**/__tests__/integration/**/*.{test,spec}.{js,ts}',
      ],
      rules: {
        // 統合テストでは長いテスト名を許可
        'jest/lowercase-name': 'off',
        // 統合テストでは複数のアサーションを許可
        'jest/expect-expect': 'off',
      },
    },
  ],
};

よくある設定ミスと対処法

環境設定の問題

問題 1: Jest 環境が認識されない

症状:

basherror  'describe' is not defined  no-undef
error  'it' is not defined        no-undef
error  'expect' is not defined    no-undef

原因と解決策:

javascript// ❌ 間違った設定
module.exports = {
  env: {
    browser: true,
    // jest: true が抜けている
  },
};

// ✅ 正しい設定
module.exports = {
  env: {
    browser: true,
    jest: true, // Jest グローバル変数を認識
  },

  // または、overrides で特定ファイルにのみ適用
  overrides: [
    {
      files: ['**/*.{test,spec}.{js,ts,tsx}'],
      env: {
        jest: true,
      },
    },
  ],
};

問題 2: TypeScript との連携エラー

症状:

basherror  Parsing error: Cannot read file 'tsconfig.json'

解決策:

javascript// ✅ TypeScript 設定の修正
module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module',
    project: './tsconfig.json', // 正しいパスを指定
  },

  // テストファイル専用の設定
  overrides: [
    {
      files: [
        '**/*.{test,spec}.ts',
        '**/*.{test,spec}.tsx',
      ],
      parserOptions: {
        project: './tsconfig.json', // テストファイル用の設定
      },
    },
  ],
};

ルール設定の問題

問題 3: 過度に厳しいルール設定

多くのプロジェクトで、最初から全てのルールを error レベルに設定して開発が進まなくなるケースが見られます。

javascript// ❌ 過度に厳しい設定例
rules: {
  'jest/expect-expect': 'error',           // 警告レベルが適切
  'jest/lowercase-name': 'error',          // 既存コードで多数エラー
  'jest/prefer-to-have-length': 'error',   // 自動修正可能なので警告で十分
}

// ✅ 段階的な導入設定
rules: {
  // 必須レベル(バグに直結)
  'jest/no-focused-tests': 'error',
  'jest/no-disabled-tests': 'error',
  'jest/valid-expect': 'error',

  // 推奨レベル(品質向上)
  'jest/expect-expect': 'warn',
  'jest/lowercase-name': 'warn',
  'jest/prefer-to-have-length': 'warn',

  // 導入予定(将来的に厳しくする)
  'jest/consistent-test-it': 'off', // 段階的に warn → error へ
}

問題 4: カスタムマッチャーが認識されない

症状:

basherror  Custom matcher 'toHaveBeenCalledWithRoute' is not defined  jest/expect-expect

解決策:

javascript// ✅ カスタムマッチャーの設定
rules: {
  'jest/expect-expect': [
    'warn',
    {
      assertFunctionNames: [
        'expect',
        // カスタムマッチャーを追加
        'expectRoute',
        'expectApiCall',
        'expectDatabaseState',
      ],
      additionalTestBlockFunctions: [
        // カスタムテストブロック関数
        'itBehavesLike',
        'sharedExamplesFor',
      ],
    },
  ],
}

パフォーマンスの問題

問題 5: ESLint 実行が遅い

大規模なプロジェクトで ESLint の実行時間が長くなる場合の対処法です。

javascript// ✅ パフォーマンス改善設定
module.exports = {
  // 不要なファイルを除外
  ignorePatterns: [
    'node_modules/',
    'dist/',
    'build/',
    'coverage/',
    '**/*.min.js',
  ],

  overrides: [
    {
      // テストファイルのみに Jest ルールを適用
      files: [
        '**/__tests__/**/*',
        '**/*.{test,spec}.{js,ts,tsx}',
      ],
      extends: ['plugin:jest/recommended'],

      // 特定のルールのみ有効化してパフォーマンス向上
      rules: {
        'jest/no-focused-tests': 'error',
        'jest/no-disabled-tests': 'error',
        'jest/valid-expect': 'error',
      },
    },
  ],
};

開発効率を上げる追加設定

IDE との連携強化

VS Code での自動修正設定

VS Code の設定ファイル(.vscode​/​settings.json)に以下を追加することで、ファイル保存時に自動的に ESLint の修正が適用されます。

json{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },

  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ],

  "eslint.workingDirectories": ["./"],

  // Jest テストファイルでのスニペット有効化
  "[javascript][typescript]": {
    "editor.suggest.snippetsPreventQuickSuggestions": false
  }
}

カスタムスニペットの設定

Jest テストの記述を効率化するためのスニペット設定です。

json// .vscode/jest.code-snippets
{
  "Jest Describe Block": {
    "prefix": "desc",
    "body": ["describe('$1', () => {", "  $0", "});"],
    "description": "Jest describe block"
  },

  "Jest Test Block": {
    "prefix": "test",
    "body": [
      "it('should $1', () => {",
      "  // Arrange",
      "  $2",
      "  ",
      "  // Act",
      "  $3",
      "  ",
      "  // Assert",
      "  expect($4).toBe($0);",
      "});"
    ],
    "description": "Jest test block with AAA pattern"
  }
}

pre-commit フックとの連携

コミット前に自動的に ESLint チェックを実行する設定です。

bash# husky のインストール
yarn add -D husky lint-staged

# pre-commit フックの設定
npx husky install
npx husky add .husky/pre-commit "yarn lint-staged"
json// package.json
{
  "lint-staged": {
    "**/*.{test,spec}.{js,ts,tsx}": [
      "eslint --fix",
      "git add"
    ]
  },

  "scripts": {
    "lint": "eslint **/*.{test,spec}.{js,ts,tsx}",
    "lint:fix": "eslint --fix **/*.{test,spec}.{js,ts,tsx}",
    "prepare": "husky install"
  }
}

CI/CD での自動化

GitHub Actions での ESLint 自動実行設定です。

yaml# .github/workflows/test-lint.yml
name: Test and Lint

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test-and-lint:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'yarn'

      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Run ESLint on tests
        run: yarn eslint "**/*.{test,spec}.{js,ts,tsx}" --format=json --output-file=eslint-results.json

      - name: Run Jest tests
        run: yarn test --coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v3

エラー分析とレポート機能

ESLint の結果を分析してチームで共有するための設定です。

javascript// scripts/eslint-analysis.js
const fs = require('fs');
const { execSync } = require('child_process');

// ESLint 実行とレポート生成
function generateLintReport() {
  try {
    // ESLint を JSON 形式で実行
    const result = execSync(
      'yarn eslint "**/*.{test,spec}.{js,ts,tsx}" --format=json',
      { encoding: 'utf8' }
    );

    const eslintResults = JSON.parse(result);

    // 統計情報の計算
    const stats = {
      totalFiles: eslintResults.length,
      totalErrors: 0,
      totalWarnings: 0,
      ruleBreakdown: {},
    };

    eslintResults.forEach((file) => {
      file.messages.forEach((message) => {
        if (message.severity === 2) stats.totalErrors++;
        if (message.severity === 1) stats.totalWarnings++;

        const rule = message.ruleId || 'unknown';
        stats.ruleBreakdown[rule] =
          (stats.ruleBreakdown[rule] || 0) + 1;
      });
    });

    // レポートファイルの生成
    fs.writeFileSync(
      'lint-report.json',
      JSON.stringify(stats, null, 2)
    );
    console.log('Lint report generated: lint-report.json');
  } catch (error) {
    console.error('ESLint analysis failed:', error.message);
    process.exit(1);
  }
}

generateLintReport();

まとめ

Jest と ESLint の連携は、テストコードの品質向上と開発効率の改善において重要な役割を果たします。本記事でご紹介した設定とベストプラクティスを参考に、プロジェクトの状況に応じて段階的に導入することをお勧めいたします。

特に重要なポイントは以下の通りです:

  1. 段階的な導入: 最初から全てのルールを厳しく設定せず、必須ルールから始めて徐々に拡張
  2. プロジェクト特性の考慮: React、Next.js、Node.js など、技術スタックに応じた適切な設定
  3. 開発ワークフローとの統合: IDE、pre-commit フック、CI/CD との連携で自動化を実現
  4. 継続的な改善: チームフィードバックと ESLint レポートを活用した設定の最適化

適切に設定された Jest と ESLint の組み合わせは、コードレビューの効率化、バグの早期発見、そしてチーム全体のテスト技術向上に大きく貢献するでしょう。ぜひ、実際のプロジェクトで活用してみてください。

関連リンク