Retour à l'accueil

React Testing Library : Abandonner les snapshots

L'article décrit la transition d'Enzyme vers React Testing Library dans le projet. Analyse des problèmes des tests snapshot et d'implémentation. Listes de vérification pour écrire et relire les tests, recommandations pour les meetups et la migration.

RTL au lieu d'Enzyme : tests de comportement React
Advertisement 728x90

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.

Google AdInline article slot

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 :

Google AdInline article slot
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.

Google AdInline article slot

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

Advertisement 728x90

Lire ensuite