スナップショットテストといえばJestsnap-shot-itなどが有名です。 しかし、どちらもそれ自体がAssertionを含むため、比較したいだけには大掛かりな仕組みです。

自分がスナップショットテストを書くときはいつもその場でパターン化したテストコードを書いています。

スナップショットテスト

スナップショットテストは、何かの入力を受け取り、その出力とファイルに保存しておいた前回の出力結果を比較してテストする手法だと思います。 主な目的はコードの変更して既存の機能を壊してしまうようなリグレッションを防ぐことです。

Babelプラグインのようなコード変換、パーサ、ジェネレータなど何かの入力を受け取り出力できるものに利用できます。

入力と出力の組み合わせをどんどん作っていけばテストも増えるので、テストを簡単に増やせるのが特徴的です。

スナップショットテストを書く

次のようなテストコードを書きます。(これはMochaを使っていますが大体どんなテストフレームワークでも同じことが出来ます。)

次のスナップショットでは、transformというJSONを入力に受け取り、JSONを出力する関数をテストしています。

snapshot-test.js:

const fs = require("fs");
const path = require("path");
const assert = require("assert");
const fixturesDir = path.join(__dirname, "snapshots");
// 変換する関数
const transform = require("../transform");

describe("Snapshot testing", () => {
  fs.readdirSync(fixturesDir)
    .map(caseName => {
      const normalizedTestName = caseName.replace(/-/g, " ");
      it(`Test ${normalizedTestName}`, function() {
        const fixtureDir = path.join(fixturesDir, caseName);
        const actualFilePath = path.join(fixtureDir, "input.json");
        const actualContent = JSON.parse(fs.readFileSync(actualFilePath, "utf-8"));
        const actual = transform(actualContent);
        const expectedFilePath = path.join(fixtureDir, "output.json");
        // UPDATE_SNAPSHOT=1 npm test で呼び出したときはスナップショットを更新
        if (process.env.UPDATE_SNAPSHOT) {
          fs.writeFileSync(expectedFilePath, JSON.stringify(actual, null, 4));
          this.skip(); // スキップ
          return;
        }
        // inputとoutputを比較する
        const expected = JSON.parse(fs.readFileSync(expectedFilePath, "utf-8"));
        assert.deepEqual(
          actual,
          expected,
          `
${fixtureDir}
${JSON.stringify(actual)}
`
        );
      });
    });
});

スナップショットファイルの作成

先ほどのテストに書いた fixturesDir に対して、テストケース毎のディレクトリを作っていくだけです。 それぞれのディレクトリには、入力となるinput.json、出力となるoutput.jsonを作って配置していくだけです。

├── snapshot-test.js
└── snapshots
    ├── テストケース名1
    │   ├── input.json
    │   └── output.json
    └── テストケース名2
        ├── input.json
        └── output.json

スナップショットのテスト作成

スナップショットテストの良いところは、前回の結果とずれてないかを目視で確認するのが主な目的であるところです。つまり、テストケースの期待する結果をわざわざ自分で書く必要はありません。

先ほどのテストもUPDATE_SNAPSHOT=1という環境変数を付けて実行すると、input.jsonからoutput.jsonのファイルを自動的に作成してくれます。

UPDATE_SNAPSHOT=1 npm test

この自動的に作られたoutput.jsonが期待している形ならコミットして終わりです。 次からは、環境変数をつけないで実行すれば単に比較されます。

npm test 

また新しいスナップショットテストを追加したい場合は同じようにinput.jsonを作って、output.jsonが期待するものかを確認して追加を繰り返すだけです。

既存の挙動を変えたときもUPDATE_SNAPSHOT=1 npm testで既存のスナップショットがすべて更新できるので、テストの変更に必要な労力は殆どありません(その結果が期待するものかは確認が必要です)

このようにテストケースがコピペのような形で増やせて、かつ今まで追加したスナップショットテストがリグレッションを防いでくれます。

おわりに

すべてのパターンに使えるテスト方法ではありませんが、変換ツールやCLIといったものをテストする際には費用対効果が良いのでおすすめです。 Jestなどを使わずにスナップショットテストを書くメリットとしてはAssertionを自由にカスタマイズできる点です。assert.deepEqual(actual, expected);はテスト内容によって異なるAssertionを使えます。

スナップショットテストを使ってる例: