Skip to content

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() :

ts
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 :

text
your-project/
├── snap.test.ts
└── __snapshots__/
    └── snap.test.ts.snap

Le fichier de snapshot contient :

ts
// 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 :

bash
bun test --update-snapshots

Ceci 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 :

ts
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 :

ts
import { test, expect } from "bun:test";

test("snapshot inline", () => {
  expect({ hello: "world" }).toMatchInlineSnapshot(`
{
  "hello": "world",
}
`);
});

Utiliser des snapshots inline

  1. Écrivez votre test avec .toMatchInlineSnapshot()
  2. Exécutez le test une fois
  3. Bun met automatiquement à jour votre fichier de test avec le snapshot
  4. 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() :

ts
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 :

ts
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 :

ts
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 :

ts
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 :

ts
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 :

tsx
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é :

ts
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 :

txt
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 :

ts
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

ts
// 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

ts
// 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

ts
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

ts
// 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 :

bash
# 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 :

txt
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 :

text
tests/
├── components/
│   ├── Button.test.tsx
│   └── __snapshots__/
│       └── Button.test.tsx.snap
├── utils/
│   ├── formatters.test.ts
│   └── __snapshots__/
│       └── formatters.test.ts.snap

Dépannage

Échecs de snapshot

Lorsque les snapshots échouent, vous verrez un diff :

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 :

ts
// 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();
});

Bun édité par www.bunjs.com.cn