Des captures d’écran au comportement utilisateur : migrer vers React Testing Library
Dans les projets classiques, les tests se concentrent souvent sur la structure du code plutôt que sur la fonctionnalité réelle. Les tests de capture (snapshots) enregistrent l’état du DOM — mais ils échouent à la moindre refonte : changement de classes CSS, réorganisation des attributs ou optimisation du rendu. Les bugs logiques réels restent alors invisibles.
Un exemple problématique avec Enzyme
describe("UserProfileCards", () => {
it("should render", () => {
const wrapper = shallow(
<Provider store={store}>
<UserProfileCards items={mockedUserProfiles} />
</Provider>
)
expect(wrapper).toMatchSnapshot()
})
})
Ce test ne donne aucune assurance réelle sur le comportement du composant. Il vérifie la structure — pas ce que l’utilisateur ressent réellement.
Dépendance aux implémentations : faux positifs
Les tests liés aux détails internes posent un double problème : ils échouent lors des refactorisations et manquent les vrais bugs.
Prenons un composant CollapseIcon avec un état opened :
import CollapseIcon from "./CollapseIcon"
import { shallow } from "enzyme"
describe("CollapseIcon", () => {
it("should render", () => {
const wrapper = shallow(<CollapseIcon opened={false} />)
expect(wrapper).toMatchSnapshot()
})
it("should react to closed", () => {
const wrapper = shallow(<CollapseIcon opened={false} />)
expect(wrapper.find(".collapse__icon").hasClass("collapseIcon_closed")).toEqual(true)
})
it("should react to opened", () => {
const wrapper = shallow(<CollapseIcon opened={true} />)
expect(wrapper.find(".collapse__icon").hasClass("collapseIcon_opened")).toEqual(true)
})
})
Problèmes :
- Les snapshots n’attrapent pas les erreurs dans la logique d’animation.
- Les vérifications de classe tombent en panne lorsqu’on restructure les styles.
- Les tests passent même si les animations sont cassées.
Un autre cas : une forte dépendance aux constantes de localisation :
it("should render no-answers button", () => {
const button = getButton(mockCommentsList[0])
const noAnswersText = `${mockedTranslation.t(`${tNamespace}no-answers`)}
expect(button.text()).toBe(noAnswersText)
})
Changer la clé dans tNamespace fait échouer le test — même si le texte du bouton est correct. Solution : déplacer les constantes dans un module partagé importé à la fois par le composant et les tests.
Des descriptions de tests floues aggravent encore la situation :
- « should show tooltip » — sans conditions.
- « should render without tooltipLabel » — sans résultat attendu.
React Testing Library : tester le comportement utilisateur
RTL repense complètement la logique : les tests simulent les actions réelles d’un utilisateur et ignorent les détails internes. Aucun accès à l’état, aux props ou aux méthodes — seulement l’interface visible.
Avantages de RTL :
- Focus utilisateur : clics, saisies, lecture d’écran.
- Sécurité en refactoring : indépendant des noms de classes, IDs ou états internes.
- API :
render(),screen.getByRole(),fireEvent.click(),waitFor(). - Prise en charge des async :
findByRole()pour le chargement de données. - Accessibilité :
getByLabelText()garantit une bonne labellisation.
RTL est conçu pour résister aux changements : les tests reflètent l’usage réel, pas la structure du DOM.
Check-list pour des tests de qualité
Écrire des tests
- Objectif clair : définir précisément ce qu’on teste (ex. : « affiche un tooltip quand isTooltipShow=true »).
- Vision utilisateur : agir comme un vrai utilisateur —
getByRole('button'),fireEvent. - Isolation : mocker les API ; aucun lien entre tests.
- Descriptions : phrases complètes lisibles sans code.
- Couverture : parcours normal + cas limites + états d’erreur.
Examiner les tests
- Le but est-il clair ?
- Les détails d’implémentation (classes, états) sont-ils évités ?
- Le test est-il isolé ?
- Les descriptions sont-elles utiles ?
- Les commentaires sont-ils constructifs ?
Organiser la migration de l’équipe
Organisez un atelier de migration : rassemblez les tests problématiques, analysez des exemples, passez progressivement à RTL. Recommandations :
- Divisez les sessions en blocs de 45 minutes avec pratique concrète.
- Refactorisez ensemble via le pair programming.
- Priorisez la pratique à la théorie.
Migration des anciens tests : écrivez de nouveaux tests en RTL, puis remplacez progressivement les anciens. Cas Airbnb : 3 500 fichiers réécrits en 6 semaines grâce à un pipeline piloté par LLM (75 % automatisé).
Points clés
- Changement de mindset : du rendu au comportement utilisateur.
- RTL vs Enzyme : RTL bloque l’accès interne ; Enzyme l’encourage.
- Check-lists : obligatoires pour écrire et relire.
- Ateliers : le dialogue vaut mieux que le monologue.
- Migration : l’automatisation accélère le processus, mais comprendre la philosophie est essentiel.
— Editorial Team
Aucun commentaire pour le moment.