跳至内容

快照

从 Vue School 通过视频学习快照

快照测试是在您希望确保函数的输出不会意外更改时非常有用的工具。

使用快照时,Vitest 会对给定值进行快照,然后将其与存储在测试旁边的参考快照文件进行比较。如果两个快照不匹配,则测试将失败:要么更改是意外的,要么需要将参考快照更新为结果的新版本。

使用快照

要对值进行快照,您可以使用来自 expect() API 的 toMatchSnapshot()

ts
import { ,  } from 'vitest'

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

第一次运行此测试时,Vitest 会创建一个如下所示的快照文件

js
// Vitest Snapshot v1, https://vitest.vuejs.ac.cn/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.vuejs.ac.cn/guide/snapshot.html

这实际上不会影响功能,但可能会影响您从 Jest 迁移时的提交差异。

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 中,快照将是

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