スナップショット
Vue School のビデオでスナップショットを学ぶスナップショットテストは、関数の出力が予期せず変更されていないことを確認したい場合に非常に便利なツールです。
スナップショットを使用すると、Vitest は指定された値のスナップショットを生成し、それをテストと共に保存されている参照スナップショットファイルと比較します。2つのスナップショットが一致しない場合、テストは失敗します。これは、変更が予期しないものであるか、参照スナップショットを新しい結果のバージョンに更新する必要があるかのいずれかを示します。
スナップショットの使用
値をスナップショットとして記録するには、expect()
API の toMatchSnapshot()
を使用できます。
import { expect, it } from 'vitest';
it('toUpperCase', () => {
const result = toUpperCase('foobar');
expect(result).toMatchSnapshot();
});
このテストが最初に実行されると、Vitest は次のようなスナップショットファイルを作成します。
// Vitest Snapshot v1, https://www.getbook.com/ja/book/vitest-1/guide/snapshot
exports['toUpperCase 1'] = '"FOOBAR"';
スナップショットの成果物はコード変更と共にコミットし、コードレビュープロセスの一環としてレビューされるべきです。その後のテスト実行では、Vitest はレンダリング結果を以前のスナップショットと比較します。一致すればテストは合格します。一致しない場合、テストランナーがコードに修正すべきバグを発見したか、実装が変更され、スナップショットを更新する必要があるかのいずれかです。
WARNING
非同期並列テストでスナップショットを使用する場合、正しいテストが検出されるように、ローカルの テストコンテキスト からの expect
を使用する必要があります。
インラインスナップショット
同様に、toMatchInlineSnapshot()
を使用して、スナップショットをテストファイル内にインラインで保存することができます。
import { expect, it } from 'vitest';
it('toUpperCase', () => {
const result = toUpperCase('foobar');
expect(result).toMatchInlineSnapshot();
});
スナップショットファイルを作成する代わりに、Vitest はテストファイルを直接変更して、スナップショットを文字列として更新します。
import { expect, it } from 'vitest';
it('toUpperCase', () => {
const result = toUpperCase('foobar');
expect(result).toMatchInlineSnapshot('"FOOBAR"');
});
これにより、別のファイルを参照することなく、期待される出力を直接確認できます。
WARNING
非同期並列テストでスナップショットを使用する場合、正しいテストが検出されるように、ローカルの テストコンテキスト からの expect
を使用する必要があります。
スナップショットの更新
受け取った値がスナップショットと一致しない場合、テストは失敗し、その差分が表示されます。スナップショットの変更が予期される場合、現在の状態からスナップショットを更新したい場合があります。
ウォッチモードでは、ターミナルで u
キーを押すと、失敗したスナップショットを直接更新できます。
または、CLI で --update
または -u
フラグを使用して Vitest にスナップショットを更新させることもできます。
vitest -u
ファイルスナップショット
toMatchSnapshot()
を呼び出すと、すべてのスナップショットはフォーマットされたスナップファイルに保存されます。つまり、スナップショット文字列内の一部の文字(具体的には二重引用符 "
とバッククォート `
)をエスケープする必要があります。その結果、スナップショットコンテンツのシンタックスハイライト(特定の言語で記述されている場合)が失われる可能性があります。
これを考慮し、ファイルと明示的に一致させるための toMatchFileSnapshot()
を導入しました。これにより、スナップショットファイルに任意のファイル拡張子を割り当てることができ、より読みやすくなります。
import { expect, it } from 'vitest';
it('render basic', async () => {
const result = renderHTML(h('div', { class: 'foo' }));
await expect(result).toMatchFileSnapshot('./test/basic.output.html');
});
これは ./test/basic.output.html
の内容と比較され、--update
フラグを使用して書き戻すことが可能です。
画像スナップショット
jest-image-snapshot
を使用して画像をスナップショットすることも可能です。
npm i -D jest-image-snapshot
test('image snapshot', () => {
expect(readFileSync('./test/stubs/input-image.png')).toMatchImageSnapshot();
});
カスタムシリアライザー
スナップショットがどのようにシリアライズされるかを変更する独自の処理を追加できます。Jest と同様に、Vitest には組み込みの JavaScript 型、HTML 要素、ImmutableJS、および React 要素のデフォルトシリアライザーがあります。
expect.addSnapshotSerializer
API を使用して、カスタムシリアライザーを明示的に追加できます。
expect.addSnapshotSerializer({
serialize(val, config, indentation, depth, refs, printer) {
// `printer` は既存のプラグインを使用して値をシリアライズする関数です。
return `Pretty foo: ${printer(val.foo, config, indentation, depth, refs)}`;
},
test(val) {
return val && Object.prototype.hasOwnProperty.call(val, 'foo');
},
});
また、カスタムシリアライザーを自動的に追加するための snapshotSerializers オプションもサポートしています。
import { SnapshotSerializer } from 'vitest';
export default {
serialize(val, config, indentation, depth, refs, printer) {
// `printer` は既存のプラグインを使用して値をシリアライズする関数です。
return `Pretty foo: ${printer(val.foo, config, indentation, depth, refs)}`;
},
test(val) {
return val && Object.prototype.hasOwnProperty.call(val, 'foo');
},
} satisfies SnapshotSerializer;
import { defineConfig } from 'vite';
export default defineConfig({
test: {
snapshotSerializers: ['path/to/custom-serializer.ts'],
},
});
次のようなテストを追加した後:
test('foo snapshot test', () => {
const bar = {
foo: {
x: 1,
y: 2,
},
};
expect(bar).toMatchSnapshot();
});
次のスナップショットが得られます。
Pretty foo: Object {
"x": 1,
"y": 2,
}
スナップショットのシリアライズには Jest の pretty-format
を使用しています。詳細については、こちらを参照してください: pretty-format。
Jest との違い
Vitest は、いくつかの例外を除き、Jest の スナップショット機能とほぼ互換性のある機能を提供します。
1. スナップショットファイルのコメントヘッダーが異なります {#_1-comment-header-in-the-snapshot-file-is-different}
- // Jest Snapshot v1, https://goo.gl/fbAQLP
+ // Vitest Snapshot v1, https://www.getbook.com/ja/book/vitest-1/guide/snapshot
これは機能には影響しませんが、Jest から移行する際にコミット差分に現れる可能性があります。
2. printBasicPrototype
のデフォルトが false
になっています {#_2-printbasicprototype-is-default-to-false}
Jest と Vitest の両方のスナップショットは pretty-format
によって提供されています。Vitest では、よりクリーンなスナップショット出力を提供するために printBasicPrototype
のデフォルトを false
に設定していますが、Jest <29.0.0 ではデフォルト値が true
です。
import { expect, test } from 'vitest';
test('snapshot', () => {
const bar = [
{
foo: 'bar',
},
];
// Jest の場合
expect(bar).toMatchInlineSnapshot(`
Array [
Object {
"foo": "bar",
},
]
`);
// Vitest の場合
expect(bar).toMatchInlineSnapshot(`
[
{
"foo": "bar",
},
]
`);
});
これは可読性と全体的な開発者体験(DX)にとって、より合理的なデフォルトであると私たちは考えます。それでも Jest の動作が好まれる場合は、設定を変更できます。
// vitest.config.js
export default defineConfig({
test: {
snapshotFormat: {
printBasicPrototype: true,
},
},
});
3. カスタムメッセージの区切り文字としてコロン :
の代わりに山括弧 >
が使用されます {#_3-chevron-is-used-as-a-separator-instead-of-colon-for-custom-messages}
Vitest は、スナップショットファイルの作成時にカスタムメッセージが渡されると、可読性向上のためコロン :
の代わりに山形括弧 >
を区切り文字として用います。
次のテストコードの例では:
test('toThrowErrorMatchingSnapshot', () => {
expect(() => {
throw new Error('error');
}).toThrowErrorMatchingSnapshot('hint');
});
Jest では、スナップショットは以下のようになります。
exports[`toThrowErrorMatchingSnapshot: hint 1`] = `"error"`;
Vitest では、同等のスナップショットは以下のようになります。
exports[`toThrowErrorMatchingSnapshot > hint 1`] = `[Error: error]`;
4. toThrowErrorMatchingSnapshot
および toThrowErrorMatchingInlineSnapshot
のデフォルトの Error
スナップショットが異なります {#_4-default-error-snapshot-is-different-for-tothrowerrormatchingsnapshot-and-tothrowerrormatchinginlinesnapshot}
import { expect, test } from 'vitest';
test('snapshot', () => {
// Jest と Vitest の両方
expect(new Error('error')).toMatchInlineSnapshot(`[Error: error]`);
// Jest は `Error` インスタンスの `Error.message` をスナップショットとして記録します
// Vitest は toMatchInlineSnapshot と同じ値を出力します
expect(() => {
throw new Error('error');
}).toThrowErrorMatchingInlineSnapshot(`"error"`);
}).toThrowErrorMatchingInlineSnapshot(`[Error: error]`);
});