J'ai écrit un générateur de rapport personnalisé pour Jest et l'ai publié sur
GitHub . Mon générateur s'appelle Jest-snapshots-book, il crée un livre HTML de snapshots des composants d'une application React.

L'article expliquera ce qu'est Jest, le test d'instantané, qui nécessite généralement un générateur de rapport supplémentaire et comment les écrire. Fondamentalement, tout cela s'applique aux tests des composants React, mais théoriquement, cela peut être appliqué lorsque vous travaillez avec des données sérialisables.
Composant React Paginator
Par exemple, dans l'article, nous allons tester le composant
paginateur (
Paginator ). Cela fait partie de notre projet de création d'applications sans serveur dans AWS (
GitHub ). La tâche d'un tel composant est d'afficher des boutons pour naviguer à travers les pages d'un tableau ou autre chose.
Il s'agit d'un simple composant fonctionnel sans composant sans état (composant sans état). En entrée, il reçoit des accessoires le nombre total de pages, la page en cours et la fonction gestionnaire de cliquer sur la page. En sortie, le composant produit un paginateur formé. Pour afficher les boutons, un autre composant
Button enfant est utilisé. S'il y a beaucoup de pages, le paginateur ne les affiche pas toutes, les combinant et les affichant sous forme de points de suspension.

Code du composant Paginatorimport React from 'react'; import classes from './Paginator.css'; import Button from '../../UI/Button/Button'; const Paginator = (props) => { const { tp, cp, pageClickHandler } = props; let paginator = null; if (tp !== undefined && tp > 0) { let buttons = []; buttons.push( <Button key={`pback`} disabled={cp === 1} clicked={(cp === 1 ? null : event => pageClickHandler(event, 'back'))}> ← </Button> ); const isDots = (i, tp, cp) => i > 1 && i < tp && (i > cp + 1 || i < cp - 1) && (cp > 4 || i > 5) && (cp < tp - 3 || i < tp - 4); let flag; for (let i = 1; i <= tp; i++) { const dots = isDots(i, tp, cp) && (isDots(i - 1, tp, cp) || isDots(i + 1, tp, cp)); if (flag && dots) { flag = false; buttons.push( <Button key={`p${i}`} className={classes.Dots} disabled={true}> ... </Button> ); } else if (!dots) { flag = true; buttons.push( <Button key={`p${i}`} disabled={i === cp} clicked={(i === cp ? null : event => pageClickHandler(event, i))}> {i} </Button> ); } } buttons.push( <Button key={`pforward`} disabled={cp === tp} clicked={(cp === tp ? null : event => pageClickHandler(event, 'forward'))}> → </Button> ); paginator = <div className={classes.Paginator}> {buttons} </div> } return paginator; } export default Paginator;
Code du composant du bouton import React from 'react'; import classes from './Button.css'; const button = (props) => ( <button disabled={props.disabled} className={classes.Button + (props.className ? ' ' + props.className : '')} onClick={props.clicked}> {props.children} </button> ); export default button;
Plaisanterie
Jest est une bibliothèque open source bien connue pour le test unitaire du code JavaScript. Il a été créé et développé grâce à Facebook. Écrit dans Node.js.
En termes généraux, la signification du test se résume au fait que vous devez trouver des paramètres d'entrée pour votre code et décrire immédiatement la sortie que votre code doit produire. Lors des tests, Jest exécute votre code avec les paramètres d'entrée et vérifie le résultat par rapport à celui attendu. S'il correspond, le test réussira et sinon, il ne passera pas.
Un petit exemple de
jestjs.io .
Supposons que nous ayons un module Node.js, qui est une fonction qui ajoute deux nombres (fichier
sum.js ):
function sum(a, b) { return a + b; } module.exports = sum;
Si notre module est enregistré dans un fichier, pour le tester, nous devons créer le fichier
sum.test.js dans lequel écrire un tel code pour le tester:
const sum = require('./sum'); test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); });
Dans cet exemple, en utilisant la fonction de
test , nous avons créé un test appelé
«ajoute 1 + 2 pour égaler 3» . Le deuxième paramètre de la fonction de
test , nous passons une fonction qui effectue réellement le test.
Le test consiste dans le fait que nous exécutons notre fonction
somme avec les paramètres d'entrée
1 et
2 , passons le résultat à la fonction Jest
expect () . Ensuite, en utilisant la fonction Jest
toBe () , le résultat transmis est comparé au résultat attendu (
3 ). La fonction
toBe () appartient à la catégorie des fonctions de test Jest (matchers).
Pour les tests, allez simplement dans le dossier du projet et appelez
jest sur la ligne de commande. Jest trouvera le fichier avec l'extension
.test.js et exécutera le test. Voici le résultat qu'il produira:
PASS ./sum.test.js ✓ adds 1 + 2 to equal 3 (5ms)
Test enzymatique et instantané des composants
Le test d'instantané est une fonctionnalité relativement nouvelle de Jest. Le fait est qu'à l'aide d'une fonction de test spéciale, nous demandons à Jest d'enregistrer un instantané de notre structure de données sur le disque et, lors des tests ultérieurs, de comparer les nouveaux instantanés avec ceux précédemment enregistrés.
Dans ce cas, l'instantané n'est rien d'autre qu'une représentation textuelle des données. Par exemple, un instantané d'un objet ressemblera à ceci (la clé du tableau ici est le nom du test):
exports[`some test name`] = ` Object { "Hello": "world" } `;
Voici à quoi ressemble la fonction de test Jest, qui effectue une comparaison d'images (paramètres facultatifs):
expect(value).toMatchSnapshot(propertyMatchers, snapshotName)
La
valeur peut être n'importe quelle structure de données sérialisable. Pour la première fois, la fonction
toMatchSnapshot () écrit simplement l'instantané sur le disque; dans les temps suivants, elle effectuera déjà la comparaison.
Le plus souvent, cette technologie de test est utilisée spécifiquement pour tester les composants React, et encore plus précisément, pour tester le rendu correct des composants React. Pour ce faire, vous devez passer le composant en tant que
valeur après le rendu.
Enzyme est une bibliothèque qui simplifie considérablement le test des applications React en fournissant des fonctions de rendu de composants pratiques. Enzyme est développé chez Airbnb.
Enzyme vous permet de rendre les composants dans le code. Il existe plusieurs fonctions pratiques pour cela qui effectuent différentes options de rendu:
- rendu complet (comme dans le navigateur, rendu DOM complet);
- rendu simplifié (rendu peu profond);
- rendu statique
Nous ne nous attarderons pas sur les options de rendu, pour les tests d'instantanés, le rendu statique est suffisant, ce qui vous permet d'obtenir du code HTML statique du composant et de ses composants enfants:
const wrapper = render(<Foo title="unique" />);
Donc, nous rendons notre composant et passons le résultat à
expect () , puis appelons la fonction
.toMatchSnapshot () . La fonction informatique n'est qu'une abréviation de la fonction de
test .
... const wrapper = render(<Paginator tp={tp} cp={cp} />); it(`Total = ${tp}, Current = ${cp}`, () => { expect(wrapper).toMatchSnapshot(); }); ...
Chaque fois que le test est
exécuté, toMatchSnapshot () compare deux instantanés:
attendu (qui a été précédemment écrit sur le disque) et
actuel (qui a été obtenu pendant le test actuel).
Si les images sont identiques, le test est considéré comme réussi. S'il y a une différence dans les images, le test est considéré comme non réussi et l'utilisateur voit la différence entre les deux images sous forme de diff (comme dans les systèmes de contrôle de version).
Voici un exemple de sortie Jest lorsque le test échoue. Ici, nous voyons que nous avons un bouton supplémentaire dans l'image actuelle.

Dans cette situation, l'utilisateur doit décider quoi faire. Si des modifications de l'instantané sont prévues en raison de modifications du code du composant, il doit remplacer l'ancien instantané par un nouveau. Et si les modifications sont inattendues, vous devez rechercher un problème dans votre code.
Je vais donner un exemple complet pour tester un paginateur (fichier
Paginator.test.js ).
Pour des tests de paginateur plus pratiques, j'ai créé une fonction
snapshoot (tp, cp) qui prendra deux paramètres: le nombre total de pages et la page actuelle. Cette fonction effectuera un test avec les paramètres donnés. Il ne reste plus qu'à appeler la fonction
snapshoot () avec différents paramètres (même en boucle) et tester, tester ...
import React from 'react'; import { configure, render } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import Paginator from './Paginator'; configure({ adapter: new Adapter() }); describe('Paginator', () => { const snapshoot = (tp, cp) => { const wrapper = render(<Paginator tp={tp} cp={cp} />); it(`Total = ${tp}, Current = ${cp}`, () => { expect(wrapper).toMatchSnapshot(); }); } snapshoot(0, 0); snapshoot(1, -1); snapshoot(1, 1); snapshoot(2, 2); snapshoot(3, 1); for (let cp = 1; cp <= 10; cp++) { snapshoot(10, cp); } });
Pourquoi aviez-vous besoin d'un générateur de rapport supplémentaire
Lorsque j'ai commencé à travailler avec cette technologie de test, le sentiment d'une approche initiale inachevée ne m'a pas quitté. Après tout, les images ne peuvent être vues que sous forme de texte.
Mais que se passe-t-il si un composant produit beaucoup de code HTML lors du rendu? Voici un composant paginateur à 3 boutons. Un instantané d'un tel composant ressemblera à ceci:
exports[`Paginator Total = 1, Current = -1 1`] = ` <div class="Paginator" > <button class="Button" > ← </button> <button class="Button" > 1 </button> <button class="Button" > → </button> </div> `;
Vous devez d'abord vous assurer que la version d'origine du composant est rendue correctement. Il n'est pas très pratique de le faire simplement en affichant le code HTML sous forme de texte. Mais ce ne sont que trois boutons. Et si vous avez besoin de tester, par exemple, une table ou quelque chose d'encore plus volumineux? De plus, pour un test complet, vous devez voir de nombreuses photos. Ce sera assez gênant et difficile.
Ensuite, si le test échoue, vous devez comprendre en quoi l'apparence des composants diffère. Les différences de leur code HTML, bien sûr, vous permettront de comprendre ce qui a changé, mais encore une fois, la possibilité de voir personnellement la différence ne sera pas superflue.
En général, j'ai pensé que cela serait nécessaire pour que les images puissent être visualisées dans le navigateur sous la même forme que dans l'application. Y compris avec les styles qui leur sont appliqués. J'ai donc eu l'idée d'améliorer le processus de test des instantanés en écrivant un générateur de rapport supplémentaire pour Jest.
Pour l'avenir, c'est ce que j'ai. Chaque fois que j'exécute les tests, mon générateur met à jour le livre d'instantanés. Directement dans le navigateur, vous pouvez visualiser les composants tels qu'ils apparaissent dans l'application, ainsi que regarder immédiatement le code source des images et des différences (si le test échoue).

Générateurs de rapports Jest
Les créateurs de Jest ont fourni la possibilité d'écrire des créateurs de rapports supplémentaires. Cela se fait comme suit. Vous devez écrire un module sur Node.JS qui doit avoir une ou plusieurs de ces méthodes:
onRunStart ,
onTestStart ,
onTestResult ,
onRunComplete , qui correspondent à divers événements de la progression du test.
Ensuite, vous devez connecter votre module dans la configuration Jest. Il existe une directive spéciale pour les
journalistes à cet effet. Si vous souhaitez inclure votre générateur en plus, vous devez l'ajouter à la fin du tableau des
rapporteurs .
Après cela, Jest appellera les méthodes de votre module à certaines étapes de l'exécution du test, en transmettant les résultats actuels aux méthodes. En fait, le code de ces méthodes devrait créer les rapports supplémentaires dont vous avez besoin. Donc, en termes généraux, la création de générateurs de rapports supplémentaires ressemble.
Comment fonctionne Jest-snapshots-book
Je n'insère pas spécifiquement le code du module dans l'article, car je vais encore l'améliorer. Il se trouve sur mon GitHub, c'est le
fichier src / index.js sur la page du projet.
Mon générateur de rapports est appelé à la fin des tests. J'ai mis le code dans la
méthode onRunComplete (contextes, résultats) . Cela fonctionne comme suit.
Dans la propriété
results.testResults , le Jest transmet un tableau de résultats de test à cette fonction. Chaque résultat de test comprend un chemin d'accès au fichier de test et un tableau de messages avec les résultats. Mon générateur de rapports recherche chaque fichier de test avec un fichier d'instantané correspondant. Si un fichier d'instantané est détecté, le générateur de rapports crée une page HTML dans le livre d'instantanés et l'écrit dans le dossier livre d'instantanés du dossier racine du projet.
Pour générer une page HTML, le générateur de rapports, à l'aide de la fonction récursive
grabCSS (moduleName, css = [], level = 0), collecte tous les styles, en commençant par le composant testé et en descendant l'arborescence de tous les composants qu'il importe. Ainsi, la fonction collecte tous les styles nécessaires pour que le composant s'affiche correctement. Les styles collectés sont ajoutés à la page HTML du livre d'instantanés.
J'utilise des modules CSS dans mes projets, donc je ne sais pas si cela fonctionnera si les modules CSS ne sont pas utilisés.
Si le test réussit, le générateur insère une iFrame dans la page HTML avec l'image dans deux options d'affichage: le code source (l'image telle qu'elle est) et le composant après le rendu. L'option d'affichage dans iFrame est modifiée en cliquant avec la souris.
Si le test n'a pas été réussi, alors tout est plus compliqué. Jest ne fournit dans ce cas que le message qu'il affiche dans la console (voir la capture d'écran ci-dessus).
Il contient des différences et des informations supplémentaires sur l'échec du test. En fait, dans ce cas, nous avons essentiellement affaire à deux images: l'
attendu et l'
actuel . Si nous avons celui attendu - il est stocké sur le disque dans le dossier des instantanés, alors l'instantané Jest actuel ne le fournit pas.
Par conséquent, j'ai dû écrire du code qui applique la différence Jest prise à partir du message à l'instantané attendu et crée un instantané réel basé sur l'attendu. Après cela, le générateur affiche à côté de l'iFrame l'instantané iFrame attendu de l'instantané actuel, qui peut changer son contenu entre trois options: le code source, le composant après le rendu, diff.
Voici à quoi ressemble la sortie du générateur de rapports si vous lui définissez l'option verbose = true.

Liens utiles
PS
Le test d'instantané ne suffit pas pour tester complètement une application React. Il ne couvre que le rendu de vos composants. Il est également nécessaire de tester leur fonctionnement (réactions aux actions des utilisateurs, par exemple). Cependant, le test d'instantanés est un moyen très pratique de s'assurer que vos composants s'affichent comme prévu. Et jest-snapshots-book rend le processus un peu plus facile.