Les tests de snapshot enregistrent la sortie d'une valeur et la comparent avec les exécutions de tests futures. Ceci est particulièrement utile pour les composants UI, les objets complexes ou toute sortie qui doit rester cohérente.
Snapshots de base
Les tests de snapshot sont écrits en utilisant le matcher .toMatchSnapshot() :
import { test, expect } from "bun:test";
test("snap", () => {
expect("foo").toMatchSnapshot();
});La première fois que ce test est exécuté, l'argument de expect sera sérialisé et écrit dans un fichier de snapshot spécial dans un répertoire __snapshots__ à côté du fichier de test.
Fichiers de snapshot
Après avoir exécuté le test ci-dessus, Bun créera :
your-project/
├── snap.test.ts
└── __snapshots__/
└── snap.test.ts.snapLe fichier de snapshot contient :
// Bun Snapshot v1, https://bun.com/docs/test/snapshots
exports[`snap 1`] = `"foo"`;Lors des exécutions futures, l'argument est comparé au snapshot sur le disque.
Mise à jour des snapshots
Les snapshots peuvent être régénérés avec la commande suivante :
bun test --update-snapshotsCeci est utile lorsque :
- Vous avez intentionnellement changé la sortie
- Vous ajoutez de nouveaux tests de snapshot
- La sortie attendue a légitimement changé
Snapshots inline
Pour des valeurs plus petites, vous pouvez utiliser des snapshots inline avec .toMatchInlineSnapshot(). Ces snapshots sont stockés directement dans votre fichier de test :
import { test, expect } from "bun:test";
test("snapshot inline", () => {
// Première exécution : le snapshot sera inséré automatiquement
expect({ hello: "world" }).toMatchInlineSnapshot();
});Après la première exécution, Bun met automatiquement à jour votre fichier de test :
import { test, expect } from "bun:test";
test("snapshot inline", () => {
expect({ hello: "world" }).toMatchInlineSnapshot(`
{
"hello": "world",
}
`);
});Utiliser des snapshots inline
- Écrivez votre test avec
.toMatchInlineSnapshot() - Exécutez le test une fois
- Bun met automatiquement à jour votre fichier de test avec le snapshot
- Lors des exécutions suivantes, la valeur sera comparée au snapshot inline
Les snapshots inline sont particulièrement utiles pour des valeurs petites et simples où il est utile de voir la sortie attendue directement dans le fichier de test.
Snapshots d'erreur
Vous pouvez également enregistrer des messages d'erreur en utilisant .toThrowErrorMatchingSnapshot() et .toThrowErrorMatchingInlineSnapshot() :
import { test, expect } from "bun:test";
test("snapshot d'erreur", () => {
expect(() => {
throw new Error("Quelque chose s'est mal passé");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("Autre erreur");
}).toThrowErrorMatchingInlineSnapshot();
});Après exécution, la version inline devient :
test("snapshot d'erreur", () => {
expect(() => {
throw new Error("Quelque chose s'est mal passé");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("Autre erreur");
}).toThrowErrorMatchingInlineSnapshot(`"Autre erreur"`);
});Utilisation avancée des snapshots
Objets complexes
Les snapshots fonctionnent bien avec des objets imbriqués complexes :
import { test, expect } from "bun:test";
test("snapshot d'objet complexe", () => {
const user = {
id: 1,
name: "John Doe",
email: "john@example.com",
profile: {
age: 30,
preferences: {
theme: "dark",
notifications: true,
},
},
tags: ["developer", "javascript", "bun"],
};
expect(user).toMatchSnapshot();
});Snapshots de tableaux
Les tableaux sont également bien adaptés aux tests de snapshot :
import { test, expect } from "bun:test";
test("snapshot de tableau", () => {
const numbers = [1, 2, 3, 4, 5].map(n => n * 2);
expect(numbers).toMatchSnapshot();
});Snapshots de sortie de fonction
Enregistrez la sortie des fonctions :
import { test, expect } from "bun:test";
function generateReport(data: any[]) {
return {
total: data.length,
summary: data.map(item => ({ id: item.id, name: item.name })),
timestamp: "2024-01-01", // Fixe pour les tests
};
}
test("génération de rapport", () => {
const data = [
{ id: 1, name: "Alice", age: 30 },
{ id: 2, name: "Bob", age: 25 },
];
expect(generateReport(data)).toMatchSnapshot();
});Snapshots de composants React
Les snapshots sont particulièrement utiles pour les composants React :
import { test, expect } from "bun:test";
import { render } from "@testing-library/react";
function Button({ children, variant = "primary" }) {
return <button className={`btn btn-${variant}`}>{children}</button>;
}
test("snapshots de composant Button", () => {
const { container: primary } = render(<Button>Cliquez-moi</Button>);
const { container: secondary } = render(<Button variant="secondary">Annuler</Button>);
expect(primary.innerHTML).toMatchSnapshot();
expect(secondary.innerHTML).toMatchSnapshot();
});Matcheurs de propriété
Pour des valeurs qui changent entre les exécutions de tests (comme les timestamps ou les IDs), utilisez des matcheurs de propriété :
import { test, expect } from "bun:test";
test("snapshot avec valeurs dynamiques", () => {
const user = {
id: Math.random(), // Change à chaque exécution
name: "John",
createdAt: new Date().toISOString(), // Change aussi
};
expect(user).toMatchSnapshot({
id: expect.any(Number),
createdAt: expect.any(String),
});
});Le snapshot stockera :
exports[`snapshot avec valeurs dynamiques 1`] = `
{
"createdAt": Any<String>,
"id": Any<Number>,
"name": "John",
}
`;Sérialiseurs personnalisés
Vous pouvez personnaliser la façon dont les objets sont sérialisés dans les snapshots :
import { test, expect } from "bun:test";
// Sérialiseur personnalisé pour les objets Date
expect.addSnapshotSerializer({
test: val => val instanceof Date,
serialize: val => `"${val.toISOString()}"`,
});
test("sérialiseur personnalisé", () => {
const event = {
name: "Réunion",
date: new Date("2024-01-01T10:00:00Z"),
};
expect(event).toMatchSnapshot();
});Bonnes pratiques
Garder les snapshots petits
// Bien : Snapshots ciblés
test("formatage du nom d'utilisateur", () => {
const formatted = formatUserName("john", "doe");
expect(formatted).toMatchInlineSnapshot(`"John Doe"`);
});
// Éviter : Snapshots énormes difficiles à revoir
test("rendu de la page entière", () => {
const page = renderEntirePage();
expect(page).toMatchSnapshot(); // Cela pourrait faire des milliers de lignes
});Utiliser des noms de tests descriptifs
// Bien : Clair sur ce que représente le snapshot
test("formate la devise avec le symbole USD", () => {
expect(formatCurrency(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});
// Éviter : Peu clair sur ce qui est testé
test("test de formatage", () => {
expect(format(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});Grouper les snapshots liés
import { describe, test, expect } from "bun:test";
describe("composant Button", () => {
test("variante primary", () => {
expect(render(<Button variant="primary">Cliquez</Button>))
.toMatchSnapshot();
});
test("variante secondary", () => {
expect(render(<Button variant="secondary">Annuler</Button>))
.toMatchSnapshot();
});
test("état disabled", () => {
expect(render(<Button disabled>Désactivé</Button>))
.toMatchSnapshot();
});
});Gérer les données dynamiques
// Bien : Normaliser les données dynamiques
test("format de réponse API", () => {
const response = {
data: { id: 1, name: "Test" },
timestamp: Date.now(),
requestId: generateId(),
};
expect({
...response,
timestamp: "TIMESTAMP",
requestId: "REQUEST_ID",
}).toMatchSnapshot();
});
// Ou utiliser des matcheurs de propriété
test("réponse API avec matcheurs", () => {
const response = getApiResponse();
expect(response).toMatchSnapshot({
timestamp: expect.any(Number),
requestId: expect.any(String),
});
});Gérer les snapshots
Revoir les changements de snapshots
Lorsque les snapshots changent, examinez-les attentivement :
# Voir ce qui a changé
git diff __snapshots__/
# Mettre à jour si les changements sont intentionnels
bun test --update-snapshots
# Committer les snapshots mis à jour
git add __snapshots__/
git commit -m "Mettre à jour les snapshots après les changements UI"Nettoyer les snapshots inutilisés
Bun avertira des snapshots inutilisés :
Warning: 1 unused snapshot found:
my-test.test.ts.snap: "ancien test qui n'existe plus 1"Supprimez les snapshots inutilisés en les effaçant des fichiers de snapshot ou en exécutant les tests avec des drapeaux de nettoyage si disponibles.
Organiser les grands fichiers de snapshot
Pour les grands projets, envisagez d'organiser les tests pour garder les fichiers de snapshot gérables :
tests/
├── components/
│ ├── Button.test.tsx
│ └── __snapshots__/
│ └── Button.test.tsx.snap
├── utils/
│ ├── formatters.test.ts
│ └── __snapshots__/
│ └── formatters.test.ts.snapDépannage
Échecs de snapshot
Lorsque les snapshots échouent, vous verrez un diff :
- Expected
+ Received
Object {
- "name": "John",
+ "name": "Jane",
}Causes courantes :
- Changements intentionnels (mettre à jour avec
--update-snapshots) - Changements non intentionnels (corriger le code)
- Données dynamiques (utiliser des matcheurs de propriété)
- Différences d'environnement (normaliser les données)
Différences de plateforme
Soyez conscient des différences spécifiques à la plateforme :
// Les chemins peuvent différer entre Windows/Unix
test("opérations de fichier", () => {
const result = processFile("./test.txt");
expect({
...result,
path: result.path.replace(/\\/g, "/"), // Normaliser les chemins
}).toMatchSnapshot();
});