kubell Creator's Note

ビジネスチャット「Chatwork」のエンジニアのブログです。

ビジネスチャット「Chatwork」のエンジニアのブログです。

読者になる

ESLintの設定値が期待した値になっているか確認するテストマッチャーを作ろう

こんにちは。フロントエンド 開発部のcw-sayamaです。

前回の投稿ではESLintのスナップショットテストについて書きましたが、今回もESLintネタを一つ。

ESLintの設定値が期待した値になっているか確認するテストマッチャーを作成したので、その経緯と内容を紹介します。

テストを導入した経緯

まずテストを導入した経緯を時系列で説明すると以下のようになります。

  • Chatworkのフロントエンド ではESLintに様々なrecommendedの設定をしているが、設定したタイミングでは修正箇所が多すぎるので無効化しているものが複数ある
  • そのためどういった理由で無効化されているのかが一目で分かりにくくなっている
  • 現在はESLintの設定を変更するときには対応するコードと変更した理由をREADMEに記載してnpmパッケージ化している
  • このパッケージを単体で利用したときは期待した動作になるが、複数のパッケージを合わせて使うと設定が反映されない可能性があることに気づいた
  • この問題を改善するために、最終的な設定値が期待したものになっていることを確認する自動テストを作成した
  • さらにこのテストを行うためのマッチャーを用意することで他の場所でも再利用可能にしている

前回紹介したスナップショットテストも組み込んでいますが、今回導入したテストが異なる点として一つはREADMEで変更した理由を残せることです。

また、もう一つの理由としてスナップショットテストの場合設定が変わったとしてもあまり意識せずに更新されてしまう可能性がありますが、テストマッチャーを使った場合テストを修正するか設定を見直す必要があるので意識的に対応できる点にあります。

前回のコード

では実際のコードですが、まず最初に前回作成したESLintの設定を取得してスナップショットを行うテストを用意します。

// eslint.spec.ts

import { ESLint } from "eslint";

function getESLintConfig(filePath: string): Promise<any> {
  const eslint = new ESLint({});
  return eslint.calculateConfigForFile(filePath);
}

test("eslint", async () => {
  // lintをかける対象のファイル
  // 実際のファイルでもいいし、テスト用に用意した空のファイルでも良い
  const filePath = "./test-file.ts";
  const config = await getESLintConfig(filePath);
  expect(config).toMatchSnapshot();
});

ここでeslint-configを取得していますが、このconfigを使ってテストを行います。

Jestマッチャーの作成

まず、configを受け取って設定値が意図した値になっているかチェックする関数を作成します。

今回は no-empty-function が off になっているかをテストします。

// checker.ts

function checkUseEmptyFunction(config: any): boolean {
  const rule = config.rules["no-empty-function"];
  if (rule) {
    return rule[0] === "off";
  }
  // 未定義の場合はoffと同等なので成功とする
  return true;
}

次にこの関数を使ってテスト結果とメッセージを返すテスト関数を追加します。

// checker.ts

expect.extend({
  toBeUseEmptyFunction(config: any) {
    const pass = checkUseEmptyFunction(config);
    return pass
      ? {
          message: () => "no-empty-function が off になっています。",
          pass,
        }
      : {
          message: () => "no-empty-function が off ではありません。",
          pass,
        };
  },
});

そしてこのテスト関数をJestのマッチャーとして使えるように index.d.ts に追加します。

// index.d.ts

declare namespace jest {
  interface Matchers<R> {
    toBeUseEmptyFunction(): R;
  }
}

テストの実施

これでテストを行う準備ができたので、先ほどのspecに追加します。

// eslint.spec.ts

import { ESLint } from "eslint";
// 追加
import "./checker";

function getESLintConfig(filePath: string): Promise<any> {
  const eslint = new ESLint({});
  return eslint.calculateConfigForFile(filePath);
}

test("eslint", async () => {
  // lintをかける対象のファイル
  // 実際のファイルでもいいし、テスト用に用意した空のファイルでも良い
  const filePath = "./test-file.ts";
  const config = await getESLintConfig(filePath);
  expect(config).toMatchSnapshot();
  // 追加
  expect(config).toBeUseEmptyFunction();
});

あとはこちらを実行することで no-empty-function が off になっているかをテストできます。

テストに失敗した場合はテスト関数に設定したメッセージが出力されます。

 FAIL  ./eslint.spec.ts
  ✕ eslint (69 ms)

  ● eslint

    no-empty-function が off ではありません。

まとめ

今回はeslint-configが期待した通りに設定されているか確認する自動テストとマッチャーを作成しました。

参考になりましたら幸いです。