Passer au contenu

Snapshot

Apprenez les Snapshots par vidéo sur Vue School

Les tests par snapshot sont un outil très utile chaque fois que vous souhaitez vous assurer que la sortie de vos fonctions ne change pas de manière inattendue.

Lors de l’utilisation d’un snapshot, Vitest prend un instantané de la valeur donnée, puis le compare à un fichier de snapshot de référence stocké aux côtés du test. Le test échouera si les deux snapshots ne correspondent pas : soit le changement est inattendu, soit le snapshot de référence doit être mis à jour avec la nouvelle version du résultat.

Utiliser les Snapshots

Pour prendre un instantané d’une valeur, vous pouvez utiliser toMatchSnapshot() de l’API expect() :

import { expect, it } from 'vitest'
it('toUpperCase', () => {
const result = toUpperCase('foobar')
expect(result).toMatchSnapshot()
})

La première fois que ce test est exécuté, Vitest crée un fichier de snapshot qui ressemble à ceci :

// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports['toUpperCase 1'] = '"FOOBAR"'

L’artéfact du snapshot doit être engagé aux côtés des changements de code et examiné dans le cadre de votre processus de révision de code. Lors des exécutions de test suivantes, Vitest comparera la sortie rendue avec le snapshot précédent. S’ils correspondent, le test réussira. S’ils ne correspondent pas, soit le coureur de tests a trouvé un bug dans votre code qui doit être corrigé, soit l’implémentation a changé et le snapshot doit être mis à jour.

Snapshots en Ligne

De même, vous pouvez utiliser toMatchInlineSnapshot() pour stocker le snapshot en ligne dans le fichier de test.

import { expect, it } from 'vitest'
it('toUpperCase', () => {
const result = toUpperCase('foobar')
expect(result).toMatchInlineSnapshot()
})

Au lieu de créer un fichier de snapshot, Vitest modifiera directement le fichier de test pour mettre à jour le snapshot sous forme de chaîne :

import { expect, it } from 'vitest'
it('toUpperCase', () => {
const result = toUpperCase('foobar')
expect(result).toMatchInlineSnapshot('"FOOBAR"')
})

Cela vous permet de voir la sortie attendue directement sans sauter entre différents fichiers.

Mise à Jour des Snapshots

Lorsque la valeur reçue ne correspond pas au snapshot, le test échoue et vous montre la différence entre eux. Lorsque le changement de snapshot est attendu, vous pouvez vouloir mettre à jour le snapshot à partir de l’état actuel.

En mode watch, vous pouvez appuyer sur la touche u dans le terminal pour mettre à jour directement le snapshot échoué.

Vous pouvez également utiliser le flag --update ou -u dans la CLI pour faire mettre à jour les snapshots par Vitest.

Fenêtre de terminal
vitest -u

Snapshots de Fichier

Lors de l’appel de toMatchSnapshot(), nous stockons tous les snapshots dans un fichier de snap formaté. Cela signifie que nous devons échapper certains caractères (à savoir les guillemets doubles " et les accents graves `) dans la chaîne du snapshot. En attendant, vous pourriez perdre la coloration syntaxique pour le contenu du snapshot (s’ils sont dans un certain langage).

Dans cette optique, nous avons introduit toMatchFileSnapshot() pour correspondre explicitement à un fichier. Cela vous permet d’attribuer n’importe quelle extension de fichier au fichier de snapshot et le rend plus lisible.

import { expect, it } from 'vitest'
it('render basic', async () => {
const result = renderHTML(h('div', { class: 'foo' }))
await expect(result).toMatchFileSnapshot('./test/basic.output.html')
})

Il comparera avec le contenu de ./test/basic.output.html. Et peut être réécrit avec le flag --update.

Snapshots d’Image

Il est également possible de prendre des snapshots d’images en utilisant jest-image-snapshot.

Fenêtre de terminal
npm i -D jest-image-snapshot
test('image snapshot', () => {
expect(readFileSync('./test/stubs/input-image.png'))
.toMatchImageSnapshot()
})

Sérialiseur Personnalisé

Vous pouvez ajouter votre propre logique pour modifier la façon dont vos snapshots sont sérialisés. Comme Jest, Vitest a des sérialiseurs par défaut pour les types JavaScript intégrés, les éléments HTML, ImmutableJS et pour les éléments React.

Vous pouvez explicitement ajouter un sérialiseur personnalisé en utilisant l’API expect.addSnapshotSerializer.

expect.addSnapshotSerializer({
serialize(val, config, indentation, depth, refs, printer) {
// `printer` est une fonction qui sérialise une valeur en utilisant les plugins existants.
return `Pretty foo: ${printer(
val.foo,
config,
indentation,
depth,
refs,
)}`
},
test(val) {
return val && Object.prototype.hasOwnProperty.call(val, 'foo')
},
})

Nous supportons également l’option snapshotSerializers pour ajouter implicitement des sérialiseurs personnalisés.

import { SnapshotSerializer } from 'vitest'
export default {
serialize(val, config, indentation, depth, refs, printer) {
// `printer` est une fonction qui sérialise une valeur en utilisant les plugins existants.
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']
},
})

Après avoir ajouté un test comme celui-ci :

test('foo snapshot test', () => {
const bar = {
foo: {
x: 1,
y: 2,
},
}
expect(bar).toMatchSnapshot()
})

Vous obtiendrez le snapshot suivant :

Pretty foo: Object {
"x": 1,
"y": 2,
}

Nous utilisons pretty-format de Jest pour sérialiser les snapshots. Vous pouvez en lire davantage ici : pretty-format.

Différence avec Jest

Vitest fournit une fonction de snapshot presque compatible avec Jest avec quelques exceptions :

1. L’en-tête de commentaire dans le fichier de snapshot est différent

// Jest Snapshot v1, https://goo.gl/fbAQLP
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

Cela n’affecte pas vraiment la fonctionnalité, mais pourrait affecter votre différence de commit lors de la migration de Jest.

2. printBasicPrototype est par défaut false

Les snapshots de Jest et Vitest sont alimentés par pretty-format. Dans Vitest, nous avons défini printBasicPrototype par défaut à false pour fournir une sortie de snapshot plus claire, tandis que dans Jest <29.0.0, c’est true par défaut.

import { expect, test } from 'vitest'
test('snapshot', () => {
const bar = [
{
foo: 'bar',
},
]
// dans Jest
expect(bar).toMatchInlineSnapshot(`
Array [
Object {
"foo": "bar",
},
]
`)
// dans Vitest
expect(bar).toMatchInlineSnapshot(`
[
{
"foo": "bar",
},
]
`)
})

Nous croyons que c’est un paramètre par défaut plus raisonnable pour la lisibilité et l’expérience globale du développeur. Si vous préférez toujours le comportement de Jest, vous pouvez changer votre configuration :

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

3. Le chevron > est utilisé comme séparateur au lieu des deux-points : pour les messages personnalisés

Vitest utilise le chevron > comme séparateur plutôt que le deux-points : pour la lisibilité, lorsqu’un message personnalisé est passé lors de la création d’un fichier de snapshot.

Pour le code de test d’exemple suivant :

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

Dans Jest, le snapshot sera :

Fenêtre de terminal
exports[`toThrowErrorMatchingSnapshot: hint 1`] = `"error"`;

Dans Vitest, le snapshot équivalent sera :

Fenêtre de terminal
exports[`toThrowErrorMatchingSnapshot > hint 1`] = `[Error: error]`;

4. Le snapshot Error par défaut est différent pour toThrowErrorMatchingSnapshot et toThrowErrorMatchingInlineSnapshot

import { expect, test } from 'vitest'
test('snapshot', () => {
// dans Jest et Vitest
expect(new Error('error')).toMatchInlineSnapshot(`[Error: error]`)
// Les snapshots de Jest `Error.message` pour l'instance `Error`
// Vitest imprime la même valeur que toMatchInlineSnapshot
expect(() => {
throw new Error('error')
}).toThrowErrorMatchingInlineSnapshot(`"error"`) // [!code --]
}).toThrowErrorMatchingInlineSnapshot(`[Error: error]`) // [!code ++]
})