Configurer un projet TypeScript / React pour les tests unitaires et d'intégration avec Jest et testing-library

Je reçois souvent la demande d’un repository d’exemple, configuré pour faire tourner des tests unitaires et des tests d’intégration sur un projet TypeScript avec React.

Aujourd’hui je vous propose donc un post un peu particulier puisqu’il prendra plutôt la forme d’un tutoriel que d’un article.

N’hésitez pas à me dire si ce format vous plaît et à me donner vos idées pour de futurs articles :)

Quel objectif à la fin de ce mini-tuto ?

Si tout se déroule bien, à la fin de cet article vous devriez :

  • avoir un projet TypeScript / React configuré pour les tests avec jest et testing-library

  • pouvoir lancer vos tests unitaires d’une simple commande pour profiter d’un feedback rapide

  • pouvoir lancer vos tests d’intégrations séparément des tests unitaires d’une simple commande aussi, pour qu’ils ne se lancent pas en même temps que les tests unitaires et ralentissent ainsi votre boucle de feedback.

Première étape : créer un nouveau projet TypeScript / React

Pour cette étape, vous avez le choix. J’ai tendance à naturellement me pencher vers create-react-app ou vite pour rapidement bootstraper un environnement de développement :

create-react-app

vitejs

Pour cet article, je vais choisir vite :)

Deuxième étape : séparer code métier et application react :

Maintenant que vous avez initialisé votre projet, je vous conseille de modifier quelque peu l’architecture des dossiers pour séparer votre projet en deux :

  • un dossier /core qui contiendra toute la logique métier de votre application.

  • un dossier /app qui contiendra tous les fichiers react.

Pensez à modifier le “include” dans votre fichier tsconfig.json pour y inclure “app/src” et “core/src”

Troisième étape : installation de Jest et testing-library pour les tests unitaires

Commençons maintenant par installer jest et testing-library pour exécuter nos tests unitaires. Comme nous sommes en TypeScript, il faut penser à installer la bibliothèque des types TypeScript de Jest, et utiliser ts-jest comme runner pour que notre code TypeScript soit transpilé correctement à l’exécution des tests :

Jest

ts-jest utilise son propre fichier de configuration, vous pouvez l’initialiser en tapant la commande :

Un fichier jest.config.js a dû être généré à la racine du projet.

Pour vérifier que tout fonctionne correctement, je vous invite à créer un test tout simple dans un fichier /core/src/__tests__/it-works.test.ts (le dossier core est celui que nous avons créé à l’étape précédente).

Lancez les tests avec la commande :

Vous devriez voir votre test passer :)

testing-library & Jest

Passons maintenant à testing-library. Cette bibliothèque de tests permet de tester des applications react (entre autre) sans utiliser de shallow rendering comme le propose par exemple Enzyme.

La philosophie de cette bibliothèque est simple : plus les tests ressemblent à la façon dont les utilisateurs utilisent l’application, plus ils sont fiables et moins ils sont fragiles.

Je vous invite à regarder l’épisode 03 de Craft Overflow sur ma chaîne YouTube pour en savoir plus sur testing-library :)

Nous avons donc besoin de testing-library/react et de testing-library/jest-dom pour avoir accès aux assertions spécifiques liées au dom :

Créons maintenant un test pour vérifier que Jest et testing-library fonctionnent bien ensemble. Créez le fichier /app/src/__tests__/App.test.tsx :

Si vous relancez les tests avec la commande yarn jest vous devriez voir une erreur :

Oups… En fait ce que nous dit cette erreur, c’est que Jest n’est pas capable d’interpréter les fichiers .svg.

L’importation de fichiers statiques permet de les inclure dans le build final mais Jest ne peut pas lire ces fichiers, car ce n’est pas du JavaScript valide, il faut donc les transformer et une façon de le gérer avec Jest est tout simplement de remplacer le module importé par son nom de fichier.

On peut le faire grâce à un transformer.

Dans notre cas, il nous faut un “file transformer”. Ajoutons donc le fichier /app/file-transformer.js :

Ce fichier est en JavaScript, car il sera exécuté par Jest avant la transpilation de ts-jest.

Il nous reste à indiquer à Jest d’utiliser ce transformer, il faut donc modifier le fichier jest.config.js à la racine du projet :

Si vous relancez les tests, une nouvelle erreur apparaît, mais on avance !

Cette erreur assez cryptique vient en fait de l’option esModuleInterop du fichier tsconfig.json à la racine du projet. Cette option permet de gérer des helpers d’import relatifs à la compatibilité entre les modules ES et la façon dont TypeScript gère les imports (ou du moins c’est ce que j’en ai compris haha). Il faut donc passer cette option à true dans le fichier tsconfig.json

Ok maintenant si l’on relance nos tests ça devrait être bon non ? Eh bien…pas tout à fait :

document is not defined !

Eh oui, c’est finalement assez logique, la configuration générée par ts-jest par défaut considère que l’on travaille dans un environnement node. Et dans nodejs, il n’y a pas de variable globale document comme en JavaScript dans le browser.

Heureusement, Jest a tout prévu et il suffit de modifier l’option testEnvironment dans le fichier jest.config.js pour passer l’option de node à jsdom, qui émule donc le dom.

Après tout cela, notre test fonctionne, car il fail comme attendu !

Si vous ne voulez pas avoir à importer manuellement @testing-library/jest-dom à chaque fois, il vous suffit de créer un fichier jest-setup.js par exemple dans lequel vous importez ce module, puis l’ajouter dans l’option setupFilesAfterEnv du fichier jest.config.js :

Quatrième étape : configuration de Jest pour les tests d’intégration

Maintenant que tout est mis en place pour nos tests unitaires, créons un faux test d’intégration. Mais tout d’abord, qu’est-ce que j’entends ici par test d’intégration ?

Les tests d’intégration dans ce contexte sont des tests lents à exécuter, qui vont vérifier l’intégration avec un service externe (une base de données, un service de paiement, etc.)

Créons donc un faux test d’intégration qui simule un test qui prend beaucoup de temps à s’exécuter. Ajoutez le fichier /core/src/__tests__/it-works.integration.test.ts :

(Notez le suffixe integration.test.ts, ça nous servira juste après ;) )

Si vous lancez maintenant une commande yarn jest pour exécuter les tests unitaires, ce test va aussi être pris en compte et va donc énormément ralentir notre boucle de feedback, puisqu’à lui tout seul il va mettre quatre secondes à passer !

Il nous faut donc un moyen de pouvoir lancer les tests unitaires et les tests d’intégration séparément. Dans notre développement quotidien on lancera les tests unitaires en watch mode et les tests d’intégration seront lancés beaucoup moins fréquemment.

Une des solutions possible : créer un pattern de naming des fichiers de tests d’intégration différent et utiliser une autre configuration Jest.

Utiliser une configuration Jest propre aux tests d’intégration

Créez le fichier jest.integration.config.js. Dans ce fichier, on va reprendre la configuration d’origine, mais modifier l’option testMatch :

Ce faisant, nous indiquons à Jest de ne prendre en considération que les fichiers suffixés par integration.test.ts

Il faut maintenant indiquer à Jest dans la config de base de ne PAS prendre en compte ce pattern grâce au ! :

Il ne reste plus qu’à ajouter deux scripts dans le fichier package.json pour lancer soit les tests unitaires, soit les tests d’intégration, en spécifiant donc pour chacun le fichier de config Jest à utiliser :

Avec ça vous devriez être prêts !

Happy Coding :)

Pierre.