Aller au contenu principal
Version : Suivant

Test Snapshot

Les tests snapshot sont très utiles pour s'assurer que votre UI ne change pas soudainement.

Un cas typique de test snapshot, c'est qu'il rend un composant de l'interface utilisateur, prend un snapshot, puis le compare à un fichier snapshot de référence stocké à côté 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 composant UI.

Test de snapshot avec Jest

Une approche similaire peut être adoptée lorsqu'il s'agit de tester vos composants React. Au lieu d'effectuer le rendu de l'interface graphique, ce qui nécessiterait de construire l'ensemble de l'application, vous pouvez utiliser un moteur de rendu de test pour générer rapidement une valeur sérialisable pour votre arborescence React. Consider this example test for a Link component:

import renderer from 'react-test-renderer';
import Link from '../Link';

it('rend correctement', () => {
const tree = renderer
.create(<Link page="http://www.facebook.com">Facebook</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});

The first time this test is run, Jest creates a snapshot file that looks like this:

exports[`rend correctement 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;

L'artefact du snapshot doit être committé en même temps que les changements de code, et révisé dans le cadre de votre processus de revue de code. Jest uses pretty-format to make snapshots human-readable during code review. Lors des prochains tests, Jest comparera la sortie rendue avec le snapshot précédent. S'ils sont identiques, le test est réussi. S'ils ne sont pas identiques, soit l'exécuteur de test a trouvé un bogue dans votre code (dans le composant <Link> dans ce cas) qui doit être corrigé, soit l'implémentation a changé et le snapshot doit être mis à jour.

remarque

The snapshot is directly scoped to the data you render – in our example the <Link> component with page prop passed to it. This implies that even if any other file has missing props (say, App.js) in the <Link> component, it will still pass the test as the test doesn't know the usage of <Link> component and it's scoped only to the Link.js. De plus, le rendu du même composant avec des props différentes dans d'autres tests de snapshot n'affectera pas le premier, car les tests ne se connaissent pas entre eux.

info

Vous trouverez plus d'informations sur le fonctionnement des tests snapshot et les raisons pour lesquelles nous les avons créés dans l'article du blog. Nous vous recommandons de lire cette note de blog pour avoir une bonne idée du moment où vous devriez utiliser le test de snapshot. Nous vous recommandons également de regarder cette vidéo de egghead sur le test des snapshots avec Jest.

Mise à jour des snapshots

Il est facile de repérer l'échec d'un test snapshot après l'introduction d'un bogue. Lorsque c'est le cas, corrigez le problème et assurez-vous que vos tests snapshot passent à nouveau. Maintenant, parlons du cas où un test snapshot échoue en raison d'un changement délibéré de l'implémentation.

Une telle situation peut se produire si nous modifions intentionnellement l'adresse vers laquelle pointe le composant Link dans notre exemple.

// Mise à jour du cas de test avec un lien vers une adresse différente
it('renders correctly', () => {
const tree = renderer
.create(<Link page="http://www.instagram.com">Instagram</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});

Dans ce cas, Jest affichera cette sortie :

Puisque nous venons de mettre à jour notre composant pour qu'il pointe vers une adresse différente, il est légitime de s'attendre à des changements dans le snapshot de ce composant. Notre scénario de test snapshot échoue car le snapshot de notre composant modifié ne correspond plus à l'artefact snapshot de ce scénario de test.

Pour résoudre ce problème, nous devrons mettre à jour nos artefacts de snapshot. Vous pouvez exécuter Jest avec un indicateur qui lui dira de regénérer des snapshots :

jest --updateSnapshot

Allez-y et acceptez les changements en exécutant la commande ci-dessus. Vous pouvez également utiliser l'indicateur équivalent avec un seul caractère -u, si vous préférez pour générer à nouveau des snapshots. Cela permettra de regénérer les artefacts de snapshot pour tous les tests de snapshot qui ont échoué. Si d'autres tests de snapshots échouaient à cause d'un bug involontaire, nous devons corriger le bug avant de générer à nouveau des snapshots pour éviter d'enregistrer des snapshots du comportement bogué.

Si vous souhaitez limiter les cas de test snapshot qui sont regénérés, vous pouvez passer un indicateur supplémentaire --testNamePattern pour réenregistrer les snapshots uniquement pour les tests qui correspondent au pattern.

You can try out this functionality by cloning the snapshot example, modifying the Link component, and running Jest.

Mode snapshot interactif

Les snapshots qui ont échoué peuvent également être mis à jour de manière interactive en mode watch :

Une fois que vous entrez dans le mode snapshot interactif, Jest vous guidera à travers les snapshots qui ont échoué, un test à la fois, et vous donnera l'opportunité de réexaminer les résultats qui ont échoué.

À partir de là, vous pouvez choisir de mettre à jour ce snapshot ou de passer au suivant :

Une fois que vous avez terminé, Jest vous donnera un résumé avant de revenir au mode watch :

Snapshots en ligne

Les snapshots en ligne se comportent de manière identique aux snapshots externes (fichiers .snap), sauf que les valeurs des snapshots sont réécrites automatiquement dans le code source. Cela signifie que vous pouvez bénéficier des avantages des snapshots générés automatiquement sans avoir à passer par un fichier externe pour vous assurer que la valeur correcte a été écrite.

Exemple :

Tout d'abord, vous écrivez un test, appelant .toMatchInlineSnapshot() sans arguments :

it('rend correctement', () => {
const tree = renderer
.create(<Link page="https://example.com">Example Site</Link>)
.toJSON();
expect(tree).toMatchInlineSnapshot();
});

La prochaine fois que vous exécuterez Jest, tree sera évalué, et un snapshot sera écrit comme argument à toMatchInlineSnapshot :

it('renders correctly', () => {
const tree = renderer
.create(<Link page="https://example.com">Example Site</Link>)
.toJSON();
expect(tree).toMatchInlineSnapshot(`
<a
className="normal"
href="https://example.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Example Site
</a>
`);
});

C'est tout ce qu'il y a à faire ! Vous pouvez même mettre à jour les instantanés avec --updateSnapshot ou en utilisant le code u en mode --watch.

Par défaut, Jest gère l'écriture des snapshots dans votre code source. Cependant, si vous utilisez prettier dans votre projet, Jest le détectera et déléguera à la place le travail à prettier (y compris en honorant votre configuration).

Comparateurs de propriétés

Souvent, certains champs de l'objet que vous souhaitez snapshoter sont générés (comme les ID et les Dates). Si vous essayez de prendre un snapshot de ces objets, ils feront échouer le snapshot à chaque exécution :

it('will fail every time', () => {
const user = {
createdAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'LeBron James',
};

expect(user).toMatchSnapshot();
});

// Snapshot
exports[`will fail every time 1`] = `
{
"createdAt": 2018-05-19T23:36:09.816Z,
"id": 3,
"name": "LeBron James",
}
`;

Pour ces cas, Jest permet de fournir un comparateur asymétrique pour n'importe quelle propriété. Ces comparateurs sont vérifiés avant que le snapshot ne soit écrit ou testé, puis ils sont enregistrés dans le fichier snapshot à la place de la valeur reçue :

it('will check the matchers and pass', () => {
const user = {
createdAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'LeBron James',
};

expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
id: expect.any(Number),
});
});

// Snapshot
exports[`will check the matchers and pass 1`] = `
{
"createdAt": Any<Date>,
"id": Any<Number>,
"name": "LeBron James",
}
`;

Toute valeur donnée qui n'est pas un comparateur sera vérifiée exactement et enregistrée dans le snapshot :

it('vérifiera les valeurs et passera', () => {
const user = {
createdAt: new Date(),
name: 'Bond... James Bond',
};

expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
name: 'Bond... James Bond',
});
});

// Snapshot
exports[`will check the values and pass 1`] = `
{
"createdAt": Any<Date>,
"name": 'Bond... James Bond',
}
`;
astuce

Si le cas concerne une chaîne de caractères et non un objet, alors vous devez remplacer la partie aléatoire de cette chaîne vous-même avant de tester le snapshot.
Vous pouvez utiliser pour cela par exemple replace() et les expressions régulières.

const randomNumber = Math.round(Math.random() * 100);
const stringWithRandomData = `<div id="${randomNumber}">Lorem ipsum</div>`;
const stringWithConstantData = stringWithRandomData.replace(/id="\d+"/, 123);
expect(stringWithConstantData).toMatchSnapshot();

Other ways this can be done is using the snapshot serializer or mocking the library responsible for generating the random part of the code you're snapshotting.

Bonnes pratiques

Les snapshots sont un outil fantastique pour identifier les changements d'interface inattendus au sein de votre application - que cette interface soit une réponse API, une interface utilisateur, des journaux ou des messages d'erreur. Comme pour toute stratégie de test, il existe des bonnes pratiques que vous devez connaître et des directives à suivre pour les utiliser efficacement.

1. Traitez les snapshots comme du code

Committez les snapshots et examinez-les dans le cadre de votre processus régulier de révision du code. Cela signifie que vous devez traiter les snapshots comme vous le feriez pour tout autre type de test ou de code dans votre projet.

Assurez-vous que vos snapshots sont lisibles en les gardant bien ciblés, courts, et en utilisant des outils qui appliquent ces conventions stylistiques.

Comme mentionné précédemment, Jest utilise pretty-format pour rendre les snapshots lisibles par l'homme, mais vous pouvez trouver utile d'introduire des outils supplémentaires, comme eslint-plugin-jest avec son option no-large-snapshots, ou snapshot-diff avec sa fonctionnalité de comparaison de snapshots de composants, pour favoriser des commits d'assertions courts et ciblés.

L'objectif est de faciliter l'examen des snapshots dans les pull requests, et de lutter contre l'habitude de régénérer les snapshots lorsque les suites de tests échouent au lieu d'examiner les causes profondes de leur échec.

2. Les tests doivent être déterministes

Vos tests doivent être déterministes. L'exécution des mêmes tests plusieurs fois sur un composant qui n'a pas changé devrait produire les mêmes résultats à chaque fois. Il vous incombe de veiller à ce que les snapshots générés n'incluent pas de données spécifiques à la plateforme ou d'autres données non déterministes.

For example, if you have a Clock component that uses Date.now(), the snapshot generated from this component will be different every time the test case is run. Dans ce cas, nous pouvons simuler la méthode Date.now() pour renvoyer une valeur cohérente à chaque fois que le test est exécuté :

Date.now = jest.fn(() => 1_482_363_367_071);

Maintenant, à chaque fois que le scénario de test snapshot s'exécute, Date.now() retournera 1482363367071 de manière cohérente. Ainsi, le même snapshot sera généré pour ce composant, quel que soit le moment où le test est exécuté.

3. Utilisez des noms de snapshot descriptifs

Efforcez-vous toujours d'utiliser des noms de test et/ou de snapshot descriptifs pour les snapshots. Les meilleurs noms décrivent le contenu attendu du snapshot. Il est ainsi plus facile pour les relecteurs de vérifier les snapshots lors de la relecture, et pour quiconque de savoir si un snapshot obsolète correspond ou non au comportement correct avant la mise à jour.

Par exemple, comparons :

exports[`<UserName /> devrait gérer certains cas de test`] = `null`;

exports[`<UserName /> devrait gérer un autre cas de test`] = `
<div>
Alan Turing
</div>
`;

Avec :

exports[`<UserName /> devrait rendre null`] = `null`;

exports[`<UserName /> devrait rendre Alan Turing`] = `
<div>
Alan Turing
</div>
`;

Puisque la deuxième version décrit exactement ce que l'on attend à la sortie, il est plus facile de voir quand c'est faux :

exports[`<UserName /> devrait rendre null`] = `
<div>
Alan Turing
</div>
`;

exports[`<UserName /> devrait rendre Alan Turing`] = `null`;

Foire aux questions

Les snapshots sont-ils écrits automatiquement sur les systèmes d'intégration continue (CI) ?

Non, à partir de Jest 20, les snapshots dans Jest ne sont pas automatiquement écrits lorsque Jest est exécuté dans un système CI sans passer explicitement --updateSnapshot. On s'attend à ce que tous les snapshots fassent partie du code qui est exécuté sur CI et puisque les nouveaux snapshots passent automatiquement, ils ne devraient pas passer un test exécuté sur un système CI. Il est recommandé de toujours livrer tous les snapshots et de les conserver dans le contrôle de version.

Les fichiers de snapshot doivent-ils être commités ?

Oui, tous les fichiers snapshot doivent être committés avec les modules qu'ils couvrent et leurs tests. Ils doivent être considérés comme faisant partie d'un test, de la même manière que la valeur de toute autre assertion dans Jest. En fait, les snapshots représentent l'état des modules sources à un moment donné dans le temps. De cette façon, lorsque les modules sources sont modifiés, Jest peut savoir ce qui a changé par rapport à la version précédente. Il peut également fournir beaucoup de contexte supplémentaire lors de la révision du code grâce auquel les réviseurs peuvent mieux étudier vos changements.

Est-ce que le test snapshot fonctionne uniquement avec les composants React ?

Les composants React et React Native constituent un bon cas d'utilisation pour les tests snapshot. Cependant, les snapshots peuvent capturer n'importe quelle valeur sérialisable et devraient être utilisés chaque fois que l'objectif est de tester si la sortie est correcte. Le dépôt Jest contient de nombreux exemples de tests de la sortie de Jest lui-même, de la sortie de la bibliothèque d'assertions de Jest ainsi que des messages du journal de diverses parties de la base de code Jest. See an example of snapshotting CLI output in the Jest repo.

Quelle est la différence entre le test snapshot et le test de régression visuelle ?

Les tests snapshot et les tests de régression visuelle sont deux façons distinctes de tester les interfaces utilisateur, et ils servent des objectifs différents. Les outils de test de régression visuelle prennent des captures d'écran de pages web et comparent les images résultantes pixel par pixel. Avec le test snapshot, les valeurs sont sérialisées, stockées dans des fichiers texte et comparées à l'aide d'un algorithme de comparaison. Il y a différents compromis à prendre en compte et nous avons listé les raisons pour lesquelles les tests instantanés ont été construits dans le blog de Jest.

Est-ce que les tests snapshot remplacent les tests unitaires?

Le test snapshot n'est qu'une des 20 assertions qui sont livrées avec Jest. L'objectif des tests snapshot n'est pas de remplacer les tests unitaires existants, mais d'apporter une valeur ajoutée et de rendre les tests plus faciles. Dans certains scénarios, les tests snapshot peuvent potentiellement supprimer le besoin de tests unitaires pour un ensemble particulier de fonctionnalités (par exemple, les composants React), mais ils peuvent également fonctionner ensemble.

Quelles sont les performances des tests snapshot en termes de vitesse et de taille des fichiers générés ?

Jest a été réécrit dans un souci de performance, et les tests snapshots ne font pas exception. Les snapshots étant stockés dans des fichiers texte, cette méthode de test est rapide et fiable. Jest génère un nouveau fichier pour chaque fichier de test qui appelle le comparateur toMatchSnapshot. La taille des snapshots est assez minime : pour preuve, la taille de tous les fichiers snapshots dans la base de code Jest elle-même est inférieure à 300 Ko.

Comment résoudre les conflits dans les fichiers snapshot ?

Les fichiers snapshot doivent toujours représenter l'état actuel des modules qu'ils couvrent. Par conséquent, si vous fusionnez deux branches et rencontrez un conflit dans les fichiers snapshot, vous pouvez soit résoudre le conflit manuellement, soit mettre à jour le fichier snapshot en exécutant Jest et en inspectant le résultat.

Est-il possible d'appliquer les principes du développement piloté par les tests avec des tests snapshot ?

Bien qu'il soit possible d'écrire les fichiers snapshot manuellement, cela n'est généralement pas envisageable. Les snapshots aident à déterminer si la sortie des modules couverts par les tests est modifiée, plutôt que de donner des conseils pour concevoir le code en premier lieu.

La couverture du code fonctionne-t-elle avec les tests snapshot ?

Oui, ainsi qu'avec n'importe quel autre test.