Tester une application React
Que tester dans une application React ?
En guise de dernier chapitre, il semble pertinent d’aborder le sujet des tests, et notamment des tests unitaires. Sur une application front-end, qu’elle soit en React ou non, la question se pose régulièrement de savoir quoi tester unitairement. En effet, si l’on prend par exemple un composant qui n’est responsable que de présentation en générant du HTML, il serait laborieux de tester précisément le rendu du composant dans le navigateur. De plus, il est possible que le moindre changement dans le CSS associé au composant change le rendu au point de mettre le test en échec, ce qui n’est généralement pas le but.
Par certains aspects, il est tout de même intéressant de tester quelques composants :
-
pour tester les informations affichées ;
-
pour tester le comportement du composant en réponse à des actions de l’utilisateur.
Par ailleurs, si votre application utilise Redux, il est possible de tester totalement le store en fonction des actions dispatchées, et ainsi tester la logique métier de l’application.
Dans ce chapitre, nous écrirons tout d’abord des tests sur les composants, en utilisant la bibliothèque Enzyme. Puis des tests sur la logique d’un store Redux couplé à Redux-Saga, à l’aide de Redux Saga Test Plan.
Test unitaire de composants avec Enzyme
1. Initialisation de l’application à tester
Afin de découvrir les tests unitaires de composants React, commençons par créer l’application que nous souhaiterons tester. Celle-ci sera initialisée de la même manière que la plupart des applications créées dans ce livre, avec Parcel :
$ yarn init -y
$ yarn add -D parcel-bundler
$ yarn add react react-dom
Le fichier public/index.html est habituel également :
<div id="app"></div>
<script src="../src/index.js"></script>
Le composant principal de notre application, celui que nous allons tester, sera un formulaire de contact. Il sera réduit au strict minimum, en effet il ne contiendra qu’un seul champ permettant de saisir un nom.
Ses spécifications sont les suivantes :
-
Le champ nom sera initialisé avec la propriété contact.name.
-
Il sera éditable, autrement dit si l’on saisit une valeur celle-ci devra bien être affichée dans le champ.
-
S’il est vide, un message d’erreur devra s’afficher.
-
Lorsque le formulaire est soumis, la fonction fournie en propriété updateContact sera appelée, avec en paramètre le contact mis à jour avec le nom saisi, et ce uniquement si le champ n’est pas vide.
Écrire un tel composant ne présente aucune difficulté. Voici, par exemple, une implémentation utilisant des hooks :
import React, { useState, useCallback } from 'react'
export const ContactForm = ({ contact, updateContact }) => {
const [name, setName] = useState(contact.name)
const onChangeName = useCallback(
event => {
setName(event.target.value)
},
[setName],
)
const onSubmit = useCallback(event => {
event.preventDefault()
if (name !== '') {
updateContact({ name })
}
})
return (
<form onSubmit={onSubmit}>
<label>
Name:
<input name="name"...
Tester le store Redux et les sagas avec Redux Saga Test Plan
Tester le store Redux d’une application peut très bien se faire sans bibliothèque externe. Après tout, un reducer n’est qu’une simple fonction n’ayant aucun effet de bord, les créateurs d’action également, ainsi que les sélecteurs. Seules les sagas pourraient poser un peu plus de difficultés, mais en sachant qu’elles ne sont que des fonctions génératrices, on pourrait tout de même s’en sortir. Néanmoins, ces tests ne seraient pas réellement pertinents.
Dans cette section, nous allons voir comment grâce à Redux Saga Test Plan, on peut écrire des tests sur un store entier (ou sur un morceau de store ou service, comme nous en avons écrit dans les chapitres Concevoir une application avec Redhux et Gérer les effets de bord avec Redux-Saga), en le considérant comme une boîte noire. Autrement dit, nous vérifierons que lorsque l’on dispatche une action :
-
Les bons effets de bord sont déclenchés (dans notre cas, un appel à une API).
-
Les bonnes actions résultantes sont dispatchées.
-
Le state a bien été mis à jour (et nous utiliserons pour cela des sélecteurs).
Comme exemple accompagnant cette section, nous allons repartir de l’exemple précédent avec son ContactForm, en y ajoutant un appel à l’API de JSON Placeholder, que nous avons vue à plusieurs reprises, pour initialiser le nom du contact.
Notre store Redux sera extrêmement semblable à ce que nous avons vu dans les chapitres sur Redux et Redux Saga (dans un seul fichier pour aller à l’essentiel).
Il gère trois actions, une pour demander la récupération du contact, et deux pour gérer les cas de succès et d’erreur :
export const actionTypes = {
GET_CONTACT: 'GET_CONTACT',
GET_CONTACT_SUCCESS: 'GET_CONTACT_SUCCESS',
GET_CONTACT_FAILURE: 'GET_CONTACT_FAILURE',
}
export const actions = {
getContact: () => ({ type: actionTypes.GET_CONTACT }),
getContactSuccess: contact => ({
type: actionTypes.GET_CONTACT_SUCCESS,
payload: contact, ...
En conclusion
Ainsi se clôt notre exploration du test d’une application React. Nous aurions pu aborder beaucoup d’autres sujets et manières de tester une application. Parmi les autres outils en vogue, citons par exemple Cypress (https://www.cypress.io) particulièrement adapté pour des tests end-to-end, qui vont jusqu’à lancer un navigateur pour ouvrir l’application testée, effectuer une série d’actions, pour vérifier que les éléments attendus sont présents sur la page.
Le sujet des tests front-end n’est en rien spécifique à React. D’ailleurs, toute la section consacrée à Redux Saga Test Plan pourrait s’appliquer à n’importe quelle bibliothèque de rendu, du moment que l’on utilise Redux et Redux Saga (ce qu’il est possible de faire avec Angular ou Vue par exemple). Mais il est tout de même intéressant de voir les différentes possibilités qui s’offrent à nous lorsqu’on souhaite tester une application dans un cadre professionnel, ou pour une application que l’on souhaite stable au fil de ses versions.
Certains projets ont pour philosophie de mettre le moins de logique possible dans les composants React, au point de réduire leur fonction au simple affichage de données. Cela est généralement une bonne pratique et justifie...