コンテンツへスキップ

スナップショット

Vue Schoolのビデオでスナップショットを学ぶ

スナップショットテストは、関数の出力が予期せず変更されないことを確認したい場合に非常に役立つツールです。

スナップショットを使用する場合、Vitestは指定された値のスナップショットを取得し、それをテストと共に保存されている参照スナップショットファイルと比較します。2つのスナップショットが一致しない場合、テストは失敗します。これは、変更が予期しないか、参照スナップショットを結果の新しいバージョンに更新する必要があることを意味します。

スナップショットの使用

値のスナップショットを作成するには、expect() APIからtoMatchSnapshot()を使用できます。

ts
import { ,  } from 'vitest'

('toUpperCase', () => {
  const  = ('foobar')
  ().()
})

このテストが初めて実行されると、Vitestは次のようなスナップショットファイルを作成します。

js
// Vitest Snapshot v1, https://vitest.dokyumento.jp/guide/snapshot.html

exports['toUpperCase 1'] = '"FOOBAR"'

スナップショットアーティファクトはコード変更と共にコミットし、コードレビュープロセスの一環としてレビューする必要があります。後続のテスト実行では、Vitestはレンダリングされた出力を以前のスナップショットと比較します。一致する場合はテストはパスし、一致しない場合は、テストランナーがコードのバグを発見したか、実装が変更され、スナップショットを更新する必要があるかのいずれかです。

警告

非同期並列テストでスナップショットを使用する場合は、正しいテストが検出されるように、ローカルのテストコンテキストからのexpectを使用する必要があります。

インラインスナップショット

同様に、toMatchInlineSnapshot()を使用して、スナップショットをテストファイル内にインラインで保存することもできます。

ts
import { ,  } from 'vitest'

('toUpperCase', () => {
  const  = ('foobar')
  ().()
})

スナップショットファイルを作成する代わりに、Vitestはテストファイルを直接変更して、スナップショットを文字列として更新します。

ts
import { ,  } from 'vitest'

('toUpperCase', () => {
  const  = ('foobar')
  ().('"FOOBAR"')
})

これにより、異なるファイル間をジャンプすることなく、期待される出力を直接確認できます。

警告

非同期並列テストでスナップショットを使用する場合は、正しいテストが検出されるように、ローカルのテストコンテキストからのexpectを使用する必要があります。

スナップショットの更新

受信した値がスナップショットと一致しない場合、テストは失敗し、それらの違いが表示されます。スナップショットの変更が予想される場合は、現在の状態からスナップショットを更新できます。

ウォッチモードでは、ターミナルでuキーを押して、失敗したスナップショットを直接更新できます。

または、CLIで--updateまたは-uフラグを使用して、Vitestにスナップショットを更新させることができます。

bash
vitest -u

ファイルスナップショット

toMatchSnapshot()を呼び出すと、すべてのスナップショットはフォーマットされたスナップファイルに保存されます。つまり、スナップショット文字列では一部の文字(特に二重引用符"とバッククォート`)をエスケープする必要があります。一方、スナップショットの内容(特定の言語の場合)の構文ハイライトが失われる可能性があります。

これを改善するために、toMatchFileSnapshot()を導入して、明示的にファイルにスナップショットを作成します。これにより、スナップショットファイルに任意のファイル拡張子を割り当て、読みやすくすることができます。

ts
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を使用してイメージのスナップショットを作成することもできます。

bash
npm i -D jest-image-snapshot
ts
test('image snapshot', () => {
  expect(readFileSync('./test/stubs/input-image.png'))
    .toMatchImageSnapshot()
})

examples/image-snapshotの例で詳細を確認できます。

カスタムシリアライザ

スナップショットのシリアライズ方法を変更するための独自のロジックを追加できます。Jestと同様に、Vitestには、組み込みのJavaScript型、HTML要素、ImmutableJS、React要素に対するデフォルトのシリアライザがあります。

expect.addSnapshotSerializer APIを使用して、明示的にカスタムシリアライザを追加できます。

ts
expect.addSnapshotSerializer({
  serialize(val, config, indentation, depth, refs, printer) {
    // `printer` is a function that serializes a value using existing plugins.
    return `Pretty foo: ${printer(
      val.foo,
      config,
      indentation,
      depth,
      refs,
    )}`
  },
  test(val) {
    return val && Object.prototype.hasOwnProperty.call(val, 'foo')
  },
})

また、snapshotSerializersオプションを使用して、暗黙的にカスタムシリアライザを追加することもサポートしています。

ts
import { SnapshotSerializer } from 'vitest'

export default {
  serialize(val, config, indentation, depth, refs, printer) {
    // `printer` is a function that serializes a value using existing plugins.
    return `Pretty foo: ${printer(
      val.foo,
      config,
      indentation,
      depth,
      refs,
    )}`
  },
  test(val) {
    return val && Object.prototype.hasOwnProperty.call(val, 'foo')
  },
} satisfies SnapshotSerializer
ts
import { defineConfig } from 'vite'

export default defineConfig({
  test: {
    snapshotSerializers: ['path/to/custom-serializer.ts']
  },
})

このようなテストを追加した後

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. スナップショットファイルのコメントヘッダーが異なります

diff
- // Jest Snapshot v1, https://goo.gl/fbAQLP
+ // Vitest Snapshot v1, https://vitest.dokyumento.jp/guide/snapshot.html

これは機能には実際には影響しませんが、Jestからの移行時にコミットのdiffに影響を与える可能性があります。

2. printBasicPrototypeはデフォルトでfalseです

JestとVitestのスナップショットの両方がpretty-formatによって提供されています。Vitestでは、よりクリーンなスナップショット出力を提供するためにprintBasicPrototypeをデフォルトでfalseに設定していますが、Jest <29.0.0ではデフォルトでtrueです。

ts
import { ,  } from 'vitest'

('snapshot', () => {
  const  = [
    {
      : 'bar',
    },
  ]

  // in Jest
  ().(`
    Array [
      Object {
        "foo": "bar",
      },
    ]
  `)

  // in Vitest
  ().(`
    [
      {
        "foo": "bar",
      },
    ]
  `)
})

これは、可読性と全体的な開発者エクスペリエンスのために、より適切なデフォルトだと考えています。それでもJestの動作を好む場合は、設定を変更できます。

ts
// vitest.config.js
export default defineConfig({
  test: {
    snapshotFormat: {
      printBasicPrototype: true
    }
  }
})

3. カスタムメッセージの場合は、コロン:の代わりに山括弧>がセパレータとして使用されます

可読性を高めるために、スナップショットファイルの作成時にカスタムメッセージが渡された場合、Vitestはコロン:の代わりに山括弧>をセパレータとして使用します。

次の例となるテストコードの場合

js
test('toThrowErrorMatchingSnapshot', () => {
  expect(() => {
    throw new Error('error')
  }).toThrowErrorMatchingSnapshot('hint')
})

Jestでは、スナップショットは次のようになります。

console
exports[`toThrowErrorMatchingSnapshot: hint 1`] = `"error"`;

Vitestでは、同等のスナップショットは次のようになります。

console
exports[`toThrowErrorMatchingSnapshot > hint 1`] = `[Error: error]`;

4. toThrowErrorMatchingSnapshottoThrowErrorMatchingInlineSnapshotのデフォルトのErrorスナップショットが異なります

js
('snapshot', () => {
  //
  // in Jest
  //

  (new ('error')).(`[Error: error]`)

  // Jest snapshots `Error.message` for `Error` instance
  (() => {
    throw new ('error')
  }).(`"error"`)

  //
  // in Vitest
  //

  (new ('error')).(`[Error: error]`)

  (() => {
    throw new ('error')
  }).(`[Error: error]`)
})

MITライセンスの下でリリースされています。