Aller au contenu principal

Tests unitaires React : un guide pratique de bout en bout

Maîtrisez les tests unitaires React, de la configuration à la CI/CD. Ce guide couvre Jest, RTL, les hooks, l'asynchrone code, le mocking et les meilleures pratiques pour des apps multiplateformes robustes.

Tests unitaires React : un guide pratique de bout en bout

Vous poussez une petite modification d'UI avant le déjeuner. Cela semble anodin. Le libellé d'un bouton change, un rendu conditionnel est simplifié et un hook utilitaire gère une nouvelle branche. La pull request est propre, la revue est rapide et le déploiement est lancé.

Une heure plus tard, le support signale que la connexion ne fonctionne plus sur une plateforme. Le web semble correct. La coquille desktop a un chemin de rendu obsolète. Le build mobile se comporte différemment après un changement d'état asynchrone. Personne ne l'a vu car le code avait des tests, mais pas les bons tests, et certainement pas un système fiable autour de ces tests.

C'est le problème principal des tests unitaires React dans les équipes en production. Écrire quelques tests qui passent n'est pas difficile. Construire une suite qui vous protège réellement lors des refontes, des cycles de release, des hotfixes et du packaging multiplateforme est la partie complexe. Les apps React ne plantent pas parce qu'une équipe a oublié comment appeler render(). Elles plantent parce que les tests dérivent vers les détails d'implémentation, les comportements asynchrones sont masqués et la CI traite les tests comme une simple case à cocher plutôt que comme une barrière de release.

Le testing unitaire moderne de React fonctionne comme un système de sécurité. Un feedback rapide en local. Des tests déterministes en CI. Des limites claires entre ce qui doit être testé et ce qui ne l'est pas. C'est d'autant plus crucial quand le même code React est déployé sur des navigateurs, des conteneurs Capacitor ou des shells Electron.

Table of Contents

Why Unit Testing React Is Your Best Safety Net

Les tests unitaires sont rentables lorsqu'ils détectent l'erreur que vous pensiez impossible. En React, cela signifie souvent qu'un composant s'affiche toujours, mais que le comportement attendu par l'utilisateur a changé. Un bouton désactivé devient cliquable. Un état de chargement ne s'efface jamais. Un message de secours disparaît après un refactoring. Ces erreurs sont mineures dans code mais coûtent cher en production.

Le test de React a radicalement changé quand React Testing Library est devenu le modèle standard pour tester le comportement plutôt que l'interne, poussant les équipes vers des tests qui reflètent le comportement utilisateur plutôt que les props ou l'état des composants, comme le souligne le guide de test de React Native dans Aperçu des tests React Native. Ce changement est important car le code de React est constamment réorganisé. Les hooks bougent. Les composants se divisent. Le Context est introduit. Un test lié à la structure interne casse lors de refactorings sains. Un test lié au comportement visible, lui, survit généralement.

Ce qu'un test unitaire doit protéger

Un bon test unitaire React protège un petit contrat :

  • Sortie rendue : L'utilisateur voit-il le bon texte, label, état ou fallback ?
  • Comportement d'interaction : Le clic, la saisie ou le basculement modifient-ils correctement l'UI ?
  • Gestion des cas limites : Le composant se comporte-t-il correctement avec les inputs attendus, des données manquantes ou un chemin d'erreur ?

Un test fragile protège la mauvaise chose :

  • Fonctionnement interne du composant : Structure de l'état, méthodes privées, props d'implémentation uniquement
  • Mécanismes du framework : Si React a mis à jour un hook exactement comme vous l'attendiez en interne
  • Détails des composants enfants : Markup de composants imbriqués que vous ne cherchez pas à vérifier ici

Règle pratique : Si vous pouvez refactoriser le composant sans changer ce que l'utilisateur voit ou fait, le test ne devrait pas avoir besoin de changer non plus.

Les tests unitaires s'inscrivent aussi dans un système de test plus large. Ils ne cherchent pas à prouver que toute l'app fonctionne de bout en bout. Ils constituent la couche rapide qui détecte les régressions avant d'avoir besoin d'un test au niveau du navigateur ou d'une validation sur appareil. C'est pourquoi ils sont la première ligne de défense de toute stack raisonnable de tests automatisés pour les apps en production.

Pour les équipes React qui déploient souvent, la confiance vient de cette division du travail. Les tests unitaires détectent rapidement les régressions locales. Les tests d'intégration vérifient les points de jonction. Les tests end-to-end confirment les parcours critiques. Si vous sautez la couche unitaire, tout ce qui est plus lent en aval devra porter trop de poids.

Configurer votre environnement de test React moderne

Un environnement de test fragile crée des tests instables avant même d'avoir écrit une seule assertion. Beaucoup de développeurs blâment Jest, jsdom ou React alors que le problème sous-jacent est une configuration incohérente entre les machines locales et la CI. La solution est de rendre l'environnement prévisible. La stabilité est ici un atout.

Un espace de travail propre avec un écran affichant des tests unitaires React code dans un éditeur code.

Commencez par un runner et un environnement prévisibles

Pour une app React moderne, surtout si elle est créée avec Vite, la configuration de base devrait inclure :

  • Un test runner : Jest reste courant, surtout dans les bases de code React plus anciennes et les stacks CI d'entreprise.
  • Un environnement de type navigateur : jsdom permet aux tests de composants de rendre le rendu DOM.
  • Les utilitaires Testing Library : @testing-library/react et @testing-library/jest-dom
  • Un point d'entrée de configuration unique : Un seul fichier pour enregistrer les matchers et les mocks globaux

Le flux de travail clé renforcé par les recommandations de test de React est simple : rendre le composant dans un environnement basé sur jsdom, interroger l'interface avec des sélecteurs comme getByText ou getByRole, déclencher une interaction, puis vérifier le changement du DOM, comme décrit dans la documentation de test ReactCe workflow ne reste fiable que si chaque machine utilise le même environnement de test.

Une configuration Jest pratique ressemble généralement à ceci :

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
  moduleNameMapper: {
    '\\.(css|less|scss)$': 'identity-obj-proxy',
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  transform: {
    '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
  },
};

Si votre équipe utilise SWC au lieu de Babel, ce n'est pas un problème. L'important n'est pas le transformateur, mais la cohérence. Choisissez une approche et standardisez-la dans le repo. Si vous cherchez une excellente référence complémentaire sur les conventions de test JavaScript, le guide de Capgo sur les tests unitaires en JavaScript est un document de passation très utile pour l'équipe.

Ajoutez le fichier de setup dont votre suite aura besoin

Un bon setupTests.js évite beaucoup de bruit répétitif :

import '@testing-library/jest-dom';

Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: jest.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(),
    removeListener: jest.fn(),
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn(),
  })),
});

C'est dans ce fichier que vous comblez les lacunes d'environnement une seule fois, plutôt que dans vingt fichiers de test différents. Ajoutez des mocks pour les API dont votre UI dépend, comme matchMedia, ResizeObserver, ou IntersectionObserver, si votre bibliothèque de composants les attend.

Sans cela, les développeurs patcheront les globals au cas par cas. Cela crée des tests incohérents et des échecs difficiles à tracer. Le test passe en local pour une personne car elle a ajouté un mock manuel dans un fichier, mais la CI échoue car le setup n'était pas partagé.

Keep local and CI behavior aligned

La commande locale doit être la plus proche possible de la commande CI. Si les développeurs utilisent le mode watch avec des paramètres permissifs alors que la CI utilise une config plus stricte, vous aurez des échecs inattendus après le merge. Gardez des scripts explicites :

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:ci": "jest --runInBand --coverage"
  }
}

Un court guide permet aux nouveaux membres de l'équipe d'acquérir rapidement les mêmes bases :

The most impactful setup choice is discipline around defaults. Put aliases in config. Put environment mocks in one setup file. Use jsdom pour les tests UI et un environnement plus léger pour les utilitaires purs quand c'est possible. Moins chaque test individuel nécessite de comportement personnalisé, plus votre système devient fiable.

Écrire des tests de composants pertinents

Les organisations n'ont pas de problème pour écrire des tests. Leur problème, c'est d'écrire des tests qui ont encore de la valeur six mois plus tard.

Le pattern standard pour les tests unitaires de composants React reste le bon : render le composant, interrogez l'UI avec des sélecteurs centrés sur l'utilisateur, déclenchez une interaction et vérifiez le changement de DOM résultant, ce qui éloigne les tests des détails d'implémentation comme le state ou les props, comme décrit dans le guide de test React. L'astuce consiste à appliquer ce pattern avec retenue.

Testez l'accordéon comme un utilisateur l'utiliserait

Prenez un Accordion composant. Il affiche un bouton avec un titre. Le contenu du panneau est masqué par défaut. Cliquer sur le bouton révèle le contenu et met à jour l'état d'accessibilité.

C'est suffisant pour réaliser plusieurs tests utiles :

  1. Le rendu initial affiche le titre mais pas le contenu.
  2. Cliquer sur le déclencheur révèle le contenu.
  3. Cliquer à nouveau le replie.
  4. Les attributs d'accessibilité reflètent l'état visible.

Ce dernier point est trop souvent négligé. Si votre composant utilise aria-expanded, aria-controlsou une structure basée sur les rôles, vérifiez-les. Ce ne sont pas des détails d'implémentation. Ils font partie du contrat utilisateur.

Les meilleurs tests de composants ressemblent à un rapport de bug que vous ne voudriez jamais recevoir.

Choisissez vos requêtes selon l'intention

React Testing Library propose plusieurs styles de requêtes, mais ils ne sont pas interchangeables. Choisir la mauvaise méthode rend les tests bruyants ou trompeurs.

Type de requête Quand l'élément est trouvé Quand l'élément n'est pas trouvé Exemple de cas d'usage
getBy Retourne l'élément immédiatement Lance une erreur immédiatement Vérifier qu'un bouton ou un titre est déjà à l'écran
queryBy Retourne l'élément immédiatement Retourne null Vérifier qu'un contenu caché n'existe pas avant l'interaction
findBy Se résout quand l'élément apparaît Est rejeté après l'attente Assert async-loaded content appears after a fetch or delayed update

Un modèle mental simple peut aider :

  • Utilisez getBy pour les éléments qui doivent déjà exister.
  • Utilisez queryBy pour les éléments qui ne doivent pas encore exister.
  • Utilisez findBy lorsque l'UI change plus tard.

Si un test commence par findBy pour tout, cela signifie généralement que l'auteur ne sait pas exactement quand le composant se met à jour. Cette incertitude finit par créer de l'instabilité.

Un exemple pratique avec un accordéon

Voici un composant représentatif :

function Accordion({ title, children }) {
  const [open, setOpen] = React.useState(false);

  return (
    <section>
      <button
        aria-expanded={open}
        aria-controls="accordion-panel"
        onClick={() => setOpen(prev => !prev)}
      >
        {title}
      </button>
      {open ? (
        <div id="accordion-panel">
          {children}
        </div>
      ) : null}
    </section>
  );
}

Voici à quoi ressemblent les tests qui en valent la peine :

import { render, screen, fireEvent } from '@testing-library/react';

test('renders the accordion title and hides content initially', () => {
  render(<Accordion title="Shipping details">Delivery takes 3 days</Accordion>);

  expect(screen.getByRole('button', { name: /shipping details/i })).toBeInTheDocument();
  expect(screen.queryByText(/delivery takes 3 days/i)).not.toBeInTheDocument();
});

test('reveals content when the trigger is clicked', () => {
  render(<Accordion title="Shipping details">Delivery takes 3 days</Accordion>);

  fireEvent.click(screen.getByRole('button', { name: /shipping details/i }));

  expect(screen.getByText(/delivery takes 3 days/i)).toBeInTheDocument();
});

test('updates aria-expanded when opened', () => {
  render(<Accordion title="Shipping details">Delivery takes 3 days</Accordion>);

  const button = screen.getByRole('button', { name: /shipping details/i });
  expect(button).toHaveAttribute('aria-expanded', 'false');

  fireEvent.click(button);

  expect(button).toHaveAttribute('aria-expanded', 'true');
});

Ce qui manque est tout aussi important. Il n'y a aucune assertion sur l'état interne. Aucun contrôle pour vérifier si setOpen a été appelé. Aucun snapshot de l'arbre de rendu complet. Ces tests alourdiraient la maintenance sans renforcer la confiance.

Quelques habitudes pour renforcer vos tests de composants :

  • Privilégiez les requêtes basées sur les rôles : Les boutons, titres, dialogues, alertes et inputs devraient généralement être trouvés par leur rôle.
  • Gardez chaque test ciblé : Un comportement visible par l'utilisateur par test permet de garder les échecs lisibles.
  • Nommez vos tests selon les résultats attendus : « met à jour aria-expanded à l'ouverture » est bien plus utile que « fonctionne correctement ».

Si un composant est difficile à tester via le DOM, cela révèle souvent un problème de conception. Peut-être que l'état est caché au mauvais endroit, ou qu'il manque de balisage sémantique. De bons tests poussent souvent les équipes vers de meilleurs composants.

Tester les hooks personnalisés et la logique applicative

Les applications React cachent beaucoup de comportements importants en dehors des composants. Les transitions d'état résident dans les hooks. La validation et le formatage se trouvent dans des fonctions utilitaires. La mise en forme des données intervient souvent avant le rendu. Si vous ne testez que les composants visibles, vous passerez à côté d'une grande partie du code qui peut pourtant casser le comportement en production.

Les hooks nécessitent un environnement compatible React

Un hook personnalisé a toujours besoin de React pour s'exécuter correctement, testez-le donc avec renderHook et entourez les appels modifiant l'état avec act().

Un petit useToggle hook est un bon exemple :

import { useState, useCallback } from 'react';

export function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  const toggle = useCallback(() => setValue(current => !current), []);
  return { value, toggle };
}

Son test doit rester concentré sur le contrat public :

import { renderHook, act } from '@testing-library/react';
import { useToggle } from './useToggle';

test('returns the initial value', () => {
  const { result } = renderHook(() => useToggle(true));
  expect(result.current.value).toBe(true);
});

test('toggles the value', () => {
  const { result } = renderHook(() => useToggle(false));

  act(() => {
    result.current.toggle();
  });

  expect(result.current.value).toBe(true);
});

Ce test est utile car le hook lui-même est l'unité. Vous ne testez pas les rouages internes de React. Vous vérifiez le comportement externe du hook.

Pour les équipes produit qui créent des primitives d'interface ou de fonctionnalités réutilisables, ce pattern est crucial. Les hooks deviennent souvent l'interface partagée entre les apps, les design systems ou les outils internes. Si vous concevez des comportements réutilisables à visée commerciale, des ressources sur les hooks pour les produits de makers peuvent aider à concevoir les hooks comme des briques logicielles plutôt que de simples détails d'implémentation.

La logique pure doit rester pure dans les tests

Tout n'a pas besoin de jsdomReact ou Testing Library. Si une fonction est pure, testez-l’avec Jest seul dans un environnement Node.

Exemple :

export function formatDisplayName(firstName: string, lastName: string) {
  return `${firstName.trim()} ${lastName.trim()}`.trim();
}

Ce test doit être très simple :

import { formatDisplayName } from './formatDisplayName';

test('joins and trims both names', () => {
  expect(formatDisplayName(' Ada ', ' Lovelace ')).toBe('Ada Lovelace');
});

test('handles a missing last name', () => {
  expect(formatDisplayName('Ada', '')).toBe('Ada');
});

L'intérêt ici, c'est la vitesse et la clarté. Quand une fonction n'a pas besoin d'un arbre rendu, ne lui en donnez pas. Les outils spécifiques à React ajoutent du surcoût. Gardez les tests de logique métier courts, rapides et proches de la fonction qu'ils vérifient.

Un découpage pratique fonctionne bien :

  • Hooks : Utilisez renderHook, act(), et des wrapper providers si nécessaire.
  • Utilitaires : Utilisez Jest seul, sans DOM.
  • Logique transversale avec état : Extrayez la logique dans des helpers testables quand le test du composant devient trop complexe.

Les équipes surchargent souvent les tests de composants avec des assertions logiques qui devraient se situer plus bas dans la stack. Extraire cette logique offre deux avantages : le test du composant est plus propre, et le test de la logique est plus rapide.

Maîtriser les techniques avancées : mocking et async

La plupart des suites React peu fiables échouent à deux endroits : aux limites des dépendances et à cause du facteur temps.

C'est pourquoi les tests asynchrones et le mocking constituent la frontière entre une suite de tests gadget et une suite de confiance avant une release. Une analyse attribue 46,5 % de l'instabilité des tests à des problèmes d'environnement ou de ressources, comme le timing asynchrone, dans cette analyse des tests unitaires React,. Dans les apps React, cela correspond directement aux transitions d'état, au rendu différé, aux UI pilotées par le réseau et aux tests qui devinent au lieu d'attendre de manière déterministe.

Un tableau comparatif présentant les techniques avancées de test React, se concentrant spécifiquement sur le mocking de dépendances par rapport aux tests asynchrones.

Mocker la frontière, pas chaque couche

Le moyen le plus rapide d'écrire un test trompeur est de mocker la moitié de l'arbre de votre composant, puis d'affirmer que vos propres mocks fonctionnent.

Pour un composant qui récupère des données de compte, mockez le client réseau ou le module API. Ne mockez pas le hook, le composant de ligne enfant, le spinner de chargement ou les trois fonctions utilitaires, sauf si le test nécessite réellement une isolation à ces niveaux.

Utilisez ce jeu de règles :

  • Mockez les services externes : Clients HTTP, analytics, API réservées au navigateur, ponts natifs
  • Mockez les API de plateforme instables : matchMedia, les timers, les interfaces de preload Electron, les plugins Capacitor lorsqu'ils sont indisponibles dans jsdom
  • Évitez de mocker vos propres composants internes par défaut : hooks personnalisés, enfants simples, utilitaires locaux

Si un test réussit parce que toutes les parties complexes ont été remplacées par des fakes, il ne vous apporte que peu de confiance pour votre release.

Pour les équipes qui souhaitent des exemples et des patterns autour des API de runner, Capgo tutoriels de test est une bibliothèque de référence pratique, surtout pour l'onboarding de développeurs qui maîtrisent React mais pas encore les mécanismes de test.

Les tests async échouent quand le timing est flou

Les échecs asynchrones viennent généralement de l'une de ces trois erreurs :

  1. Le test fait son assertion trop tôt.
  2. Le test attend avec des timers arbitraires.
  3. Le composant se met à jour plusieurs fois, mais le test ne modélise qu'une seule transition.

Un test asynchrone stable a généralement cette structure :

test('shows user details after data loads', async () => {
  render(<UserProfile userId="42" />);
  expect(screen.getByText(/loading/i)).toBeInTheDocument();

  expect(await screen.findByText(/account owner/i)).toBeInTheDocument();
});

Ou, quand vous devez attendre une condition précise :

await waitFor(() => {
  expect(screen.getByRole('alert')).toBeInTheDocument();
});

Utilisez findBy quand l'apparition d'un élément est l'événement qui vous intéresse. Utilisez waitFor quand la condition est plus générale ou que l'état ne peut pas être exprimé par une seule requête. Évitez setTimeout dans les tests, sauf si vous testez explicitement le comportement des timers avec des fake timers.

L'écosystème de test de React attend aussi que vous respectiez act() la sémantique des mises à jour. Testing Library gère une grande partie de cela pour vous, mais si vous gérez l'état manuellement ou si vous avancez les timers, vous devez toujours réfléchir au moment où les mises à jour sont appliquées.

Savoir quel outil de mocking choisir

Différents outils de mocking résolvent différents problèmes :

Outil Meilleur usage Erreur courante
jest.fn() Faux callbacks autonomes ou fonctions injectées L'utiliser pour remplacer un module entier quand un simple callback suffit
jest.spyOn() Observer ou surcharger une méthode sur un objet ou un module réel Oublier de restaurer l'implémentation originale
jest.mock() Remplacer une dépendance de module à la frontière d'importation Mocker de gros modules par défaut et perdre les comportements significatifs

Les exemples aident à :

  • Utiliser jest.fn() lorsqu'un composant reçoit une onSubmit prop.
  • Utilisez jest.spyOn() lorsque vous devez vérifier console.error, une méthode de stockage, ou un appel API exporté.
  • Utilisez jest.mock() lorsqu'importer un module entraînerait des E/S, du code natif, ou un comportement hors du périmètre de l'unité.

Un domaine avancé souvent négligé par les guides est le test des chemins d'erreur dans le React moderne. Les error boundaries, les changements d'état différés et les interfaces de repli asynchrones méritent des tests de premier ordre, pas seulement l'exemple du clic sur le « happy path ». Si un enfant lève une erreur, vérifiez l'interface de repli. Si une requête échoue, vérifiez l'état de récupération visible. Si un bouton est désactivé pendant le chargement, vérifiez-l’aussi. Ce sont ces bugs que les utilisateurs retiennent.

Améliorer la qualité et la stratégie de test

Beaucoup d'équipes courent encore après la couverture de code comme si c'était synonyme de confiance. Ce n'est pas le cas.

Vous pouvez atteindre un objectif de couverture tout en passant à côté des régressions critiques. Une suite remplie d'assertions superficielles, de snapshots trop larges et de mocks internes donne une illusion de sécurité tout en augmentant les coûts de maintenance.

Une infographie comparant les bénéfices de tests de qualité face à la charge de maintenance de suites de tests volumineuses.

La couverture est une carte, pas l'objectif

Les rapports de couverture sont utiles lorsqu'ils répondent à une seule question : quels parcours critiques ne sont pas encore protégés ?

Ils ne sont pas utiles s'ils poussent les développeurs à tester des wrappers triviaux, du markup statique ou des fichiers de redirection d'une ligne juste pour faire grimper un pourcentage. Considérez la couverture comme un outil de découverte. Si l'état d'authentification, les actions de facturation, les feature flags ou les prompts de mise à jour n'ont pas de tests, c'est un signal. Si un composant d'icône purement visuel n'en a pas, ce n'en est généralement pas un.

Une bonne question de revue est simple : ce test réduit-il le risque de la release ?

  • Oui : Il vérifie un comportement visible par l'utilisateur sur un parcours critique.
  • Peut-être : Il protège une logique métier facile à casser lors d'un refactoring.
  • Non : Il vérifie des détails d'implémentation ou duplique la valeur d'un autre test.

Ce qu'il ne faut pas tester unitairement

De nombreux guides React ne consacrent toujours pas assez de temps aux omissions. Ce manque est crucial car le sur-mocking et les tests basés sur les détails d'implémentation créent des suites fragiles qui passent alors que l'expérience utilisateur est dégradée, comme le souligne le guide de BrowserStack sur ce qu'il ne faut pas tester en unité dans React.

Évitez ou limitez strictement ces patterns :

  • Assertions sur l'état interne : Ne testez pas isOpen directement quand vous pouvez tester si le panneau s'est ouvert.
  • Comportement du framework : Ne testez pas si React a appelé un effet. Testez le résultat de ce que l'effet modifie.
  • Internals de bibliothèques tierces : Testez votre intégration avec un date picker ou un router, pas la logique de rendu propre à la bibliothèque.
  • Unités trop fragmentées : Si vous avez mocké chaque enfant et chaque helper, vous ne testez peut-être plus de comportement réel.

De mauvais tests sont pires que l'absence de tests lorsqu'ils bloquent les refactors sans pour autant détecter les bugs en production.

Une heuristique utile est la propriété des frontières. Testez ce que votre code possède. Ne testez pas ce que React, le navigateur ou une bibliothèque mature gèrent déjà, sauf si votre couche d'intégration modifie le contrat.

L'utilité et les limites des snapshots

Les snapshots ne sont pas inutiles. Ils sont juste faciles à mal utiliser.

Utilisez-les avec parcimonie pour les composants ayant un rendu stable et simple, où un diff structurel large est pertinent. Évitez-les pour les composants interactifs ou très dynamiques, car ils deviennent du bruit. Les développeurs cessent de les lire et se mettent à les mettre à jour par réflexe.

Des alternatives meilleures existent généralement :

  • Pour le rendu conditionnel, vérifiez la présence ou l'absence d'un texte clé.
  • Pour les changements d'état visuels, vérifiez le rôle, le label ou l'attribut pertinent.
  • Pour les erreurs et les fallbacks, vérifiez le message réel ou la zone d'alerte.

Si votre équipe a besoin d'un processus de qualité plus large que les tests unitaires, un excellent complément est un workflow d'assurance qualité applicative qui traite les tests, les vérifications de release et la planification du rollback comme un système unique. C'est ce changement de mentalité qui améliore le plus rapidement la qualité des tests. Ne demandez plus combien de tests vous avez. Demandez-vous plutôt quels échecs pourraient encore atteindre vos utilisateurs.

Intégrer les tests dans un pipeline CI/CD multiplateforme

Une suite de tests qui ne s'exécute que sur l'ordinateur d'un développeur est une suggestion, pas un contrôle.

La suite devient opérationnelle quand chaque pull request exécute les mêmes vérifications dans un environnement propre et bloque les merges en cas d'échec. Cela semble évident, mais de nombreuses équipes laissent encore des lacunes critiques. Les tests sont lancés manuellement. Les rapports de couverture sont optionnels. Les jobs de packaging et de release démarrent avant la fin des tests. C'est ainsi que de petites régressions UI s'insèrent dans des échecs de release plus importants.

Un diagramme en cinq étapes illustrant le processus d'intégration des tests automatisés React dans un pipeline de développement CI/CD.

Une pull request doit déclencher la même barrière de contrôl’à chaque fois

Pour que les tests unitaires React servent de filet de sécurité, la CI a besoin de quelques essentiels :

  • S'exécuter à chaque pull request
  • Installer les dépendances via le lockfile
  • Utiliser la même commande de test à chaque fois
  • Échouer rapidement en cas d'échec des tests
  • Publish artifacts only after tests pass

C'est le cœur des pratiques de déploiement continu pour les équipes d'app. Gagnez en confiance avant la release, pas après.

Un simple workflow GitHub Actions suffit à de nombreuses équipes :

name: test

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  react-tests:
    runs-on: ubuntu-latest

    steps:
      - name: Check out code
        uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run unit tests
        run: npm run test:ci

Ce n'est pas sophistiqué, et c'est tout l'intérêt. Les pipelines les plus robustes sont souvent les moins surprenants.

Pourquoi c'est crucial pour Capacitor et Electron

Les apps React cross-platform présentent plus de risques de release que les apps web, car le même code UI est souvent déployé dans différents containers avec des comportements runtime distincts.

Voici quelques exemples où les pipelines sont utiles :

  • Apps Capacitor : Le code web peut passer en local mais échouer lorsqu'un bridge de plugin, un état hors ligne ou un cas limite du cycle de vie de l'app change le comportement après le packaging.
  • Apps Electron : Un composant renderer peut dépendre d'API de preload, de messaging window ou d'un état desktop qui n'existera pas lors de tests navigateur classiques, sauf s'ils sont délibérément mockés.
  • Trains de release partagés : Un mauvais bundle peut impacter plusieurs cibles si votre processus de déploiement ne verrouille pas strictement la publication.

C'est pourquoi les tests unitaires doivent s'exécuter avant les jobs de packaging, et ces derniers avant les jobs de distribution. Chaque étape réduit les risques. Les tests unitaires détectent rapidement les régressions locales. Le packaging plateforme vérifie les hypothèses d'environnement. L'approbation manuelle ou le déploiement progressif assure la confiance lors de la release finale.

Un workflow GitHub Actions concret

Un pipeline plus mature sépare généralement les responsabilités :

  1. Job de test : Tests unitaires et de hooks rapides
  2. Job de build : Production build only after tests pass
  3. Job de packaging : Sync Capacitor, packaging Electron ou bundling d'artefacts
  4. Job de release : Publiez uniquement depuis des branches ou des tags approuvés

Pour les équipes qui déploient des live updates sur Capacitor ou des apps Electron, c'est là que les outils de release deviennent essentiels. Une option dans ce workflow est Capgo, qui publie des bundles web signés pour les apps CapacitorJS et Electron avec support du rollback et contrôle du déploiement par canal. En pratique, cela signifie que votre job de test React peut servir de premier verrou avant qu'un bundle web ne soit promu vers la staging ou la production.

La règle opérationnelle est simple. Ne laissez pas l'infrastructure de release compenser des tests fragiles. Utilisez l'infrastructure de release une fois que des tests fiables ont déjà filtré les mauvais changements.

Un système de test fiable change le comportement de l'équipe. Les ingénieurs fusionnent avec moins d'hésitation. Les reviewers se concentrent sur les edge cases au lieu de re-tester manuellement les bases. Les release managers cessent de traiter chaque déploiement comme un pari. C'est le résultat d'un bon unit testing React.


Si votre équipe déploie du React via Capacitor ou Electron, la sécurité de vos releases dépend de bien plus que de simples tests locaux réussis. Capgo offre aux équipes un moyen contrôlé de publier des mises à jour web signées, de cibler des canaux de déploiement et de faire un rollback de bundles défectueux sans attendre la revue des stores, ce qui s'intègre naturellement derrière un pipeline CI exigeant des tests unitaires avant le déploiement.

Live updates pour les apps Capacitor

Quand un bug de la couche web est en production, déployez le correctif via Capgo au lieu d'attendre des jours l'approbation des stores. Les utilisateurs reçoivent la mise à jour en arrière-plan, tandis que les changements natifs suivent le circuit de validation habituel.

Les derniers articles du blog

Capgo vous apporte les meilleures informations pour créer une application mobile vraiment professionnelle.