跳至內容

快照

從 Vue School 的影片中學習快照

只要你想確保函式的輸出不會意外變更,快照測試就是一個非常有用的工具。

當使用快照時,Vitest 會擷取給定值的快照,然後將其與儲存在測試旁的參考快照檔案進行比較。如果兩個快照不符,測試就會失敗:變更可能是意外的,或者參考快照需要更新為結果的新版本。

使用快照

若要擷取值的快照,你可以從 expect() API 使用 toMatchSnapshot()

ts
import { ,  } from 'vitest'

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

第一次執行此測試時,Vitest 會建立一個快照檔案,如下所示

js
// Vitest Snapshot v1, https://vitest.dev.org.tw/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() 時,我們會將所有快照儲存在格式化的 snap 檔案中。這表示我們需要跳脫快照字串中的一些字元(即雙引號 " 和反引號 `)。同時,您可能會失去快照內容的語法突顯(如果它們是用某種語言撰寫的)。

為了改善這種情況,我們引入了 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.dev.org.tw/guide/snapshot.html

這並不會真正影響功能,但從 Jest 移轉時可能會影響您的 commit 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",
      },
    ]
  `)
})

我們相信這對於可讀性和整體 DX 來說是更合理的預設值。如果您仍然偏好 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 中,快照將會是

主控台
exports[`toThrowErrorMatchingSnapshot: hint 1`] = `"error"`;

在 Vitest 中,等效的快照將會是

主控台
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]`)
})