自身で作成した ESLint Config をスナップショットテストする
これはなに
作成した ESLint Config が期待値通りかどうかをテストする方法についてまとめたものです。
ESLint は非常に多くのルールを提供しており、これにプラグインも加えるとその数は膨大なものになります。全てのルール設定を自身で記述するには多大なコストを要するため、各種プラグインが提供する configs (≒ プリセット)を取り入れつつ、必要に応じてルール設定を追加するのが一般的です。さらに特定のファイル形式やディレクトリー配下に限定して異なる設定を適用する場合もあるでしょう。こうなると設定の全体像が極めて複雑となり、期待値通りになっているかを目視で確認するのは非常に困難です。
本稿では、このような課題の解決に役立つスナップショットテストの手法をご紹介します。
スナップショットテストとは
スナップショットテストとは、ある関数やコンポーネントの出力結果をテスト実行時に記録し、次回以降のテスト実行時にその記録と比較して差分が発生していないかを検証するテスト手法のことです。Visual Regression Testing (VRT) はその一種であり、UI コンポーネントのテストに用いられることが多いですが、今回のような設定ファイルのテストにも適用できます。
ESLint Config におけるスナップショットテスト
ESLint CLI には --print-config
オプションが用意されており、これを付与すると実行時に適用される設定内容が JSON 形式で出力されます。そこには各種ルールの設定値がフラットに全て列挙されるのはもちろん、 settings
や languageOptions
などの設定値も含まれます。
npx eslint --print-config ./target.js
# target.js に適用される ESLint の設定内容が JSON 形式で出力される
{
"settings": {...},
"languageOptions": {...},
"plugins": [...],
"rules": {...},
}
このとき Lint 自体は実施されないため、 --print-config
オプションに渡すファイルの中身は空で構いません。
また、この JSON データをテキストファイルとして保存すればそれは現在の設定内容のスナップショットとなり、作成した ESLint Config の設定内容が期待値通りであるかを検証できます。
npx eslint --print-config ./target.js > ./snapshot.json
さらに次回以降のテスト実行時には、保存したスナップショットとの差分を比較することで設定の変更を検知できます。
スナップショットテストの設計
Jest もしくは Vitest には toMatchSnapshot()
という関数が用意されており[1]、これを利用することでスナップショットテストを簡単に実装できます。
一方の ESLint ですが、テストランナー上での実行となると CLI では不都合です。ESLint にはプラグインやテストランナーでの利用を前提とした Node.js API が用意されており、これを駆使することでスナップショットテストを実装できます。
実装
1.ファイル・ディレクトリー構成
以下のようなファイル・ディレクトリー構成を想定します。
.
├── target.js # Linter に適用するファイル
├── eslint.config.js # テスト対象となる ESLint Config ファイル
└── snapshot.test.js # テストコード
2.ESLint クラスのインスタンスを生成する
まずは ESLint の Node.js API を利用して ESLint
クラスのインスタンスを生成するロジックを実装します。
import { loadESLint } from 'eslint';
const DefaultESLint = await loadESLint({
useFlatConfig: true,
});
const eslint = new DefaultESLint({
cwd: import.meta.dirname,
});
loadESlint()
メソッドを使って ESLint のクラスを取得します。 useFlatConfig
オプションを true
にすることで Flat Config に対応したクラス、 false
にすることで eslintrc format に対応したクラスを取得できます。デフォルト値は true
です。
その後、取得したクラスからインスタンスを生成する際に cwd
オプションを指定して eslint.config.js
を読み込むディレクトリーを指定します。
3.ESLint の設定内容を取得する
eslint.calculateConfigForFile(filePath)
メソッドを使って引数に渡したファイルに適用される ESLint の設定内容を取得します。
const config = eslint.calculateConfigForFile('./target.js');
CLI の --print-config
と同様に calculateConfigForFile()
メソッドの引数にも単独のファイルパスを指定します。これで ./target.js
に適用される ESLint の設定内容が取得できます。
4.スナップショットテストを実装する
先述した Jest もしくは Vitest の toMatchSnapshot()
メソッドを使って取得した設定内容をスナップショットテストとして保存します。
import { loadESLint } from 'eslint';
test('should match ESLint Configuration snapshot', async () => {
const DefaultESLint = await loadESLint({
useFlatConfig: true,
});
const eslint = new DefaultESLint({
cwd: import.meta.dirname,
});
const config = eslint.calculateConfigForFile('./target.js');
expect(config).toMatchSnapshot();
});
ESLint の Flat Config は ES Modules を前提としているため、テストコードも ES Modules として実装します。
5.テストを実行する
最後にテストを実行します。
NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules" npx jest
# Vitest は ES Modules をサポートしているため、環境変数の指定は不要。
npx vitest
初回実行時はスナップショットファイルが以下のように生成されます。
.
+├── __snapshots__/
+│ └── snapshot.test.js.snap
├── target.js
├── eslint.config.js
└── snapshot.test.js
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should match ESLint Configuration snapshot 1`] = `
{
"languageOptions": {
"ecmaVersion": "latest",
"parser": "espree@9.6.1",
"parserOptions": {
"ecmaVersion": 2023,
"sourceType": "module",
},
"sourceType": "module",
},
"plugins": [
"@",
"import",
"promise",
],
"processor": undefined,
"rules": {
"accessor-pairs": [0],
...
},
"settings": {
...
},
}
`;
2 回目以降のテスト実行時は保存したスナップショットとの差分比較となり、その結果がログとして出力されます。これでスナップショットの実装は完了です。
締め
ESLint Config は非常に複雑な設定内容となりがちなため、正しく設定したつもりが期待通りに Lint エラーが検知されず、知らずのうちにコードベースが乱れてしまうといった事象に陥りやすいのが難点です。スナップショットテストを導入することで ESlint Config が適切に記述されているかを継続的に検証でき、コード品質を維持するための一助となります。
参考文献
-
Vitest は Jest に対して高い互換性を持つテストランナーのため、Jest の殆どの機能は Vitest でも利用可能です。 ↩︎
Discussion