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
- Configurer votre environnement de test React moderne
- Écrire des tests de composants pertinents
- Tester les hooks personnalisés et la logique applicative
- Maîtriser les techniques avancées : mocking et async
- Améliorer la qualité et la stratégie de test
- Intégrer les tests dans un pipeline CI/CD multiplateforme
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.

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 :
jsdompermet aux tests de composants de rendre le rendu DOM. - Les utilitaires Testing Library :
@testing-library/reactet@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 :
- Le rendu initial affiche le titre mais pas le contenu.
- Cliquer sur le déclencheur révèle le contenu.
- Cliquer à nouveau le replie.
- 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
getBypour les éléments qui doivent déjà exister. - Utilisez
queryBypour les éléments qui ne doivent pas encore exister. - Utilisez
findBylorsque 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.

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 :
- Le test fait son assertion trop tôt.
- Le test attend avec des timers arbitraires.
- 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 uneonSubmitprop. - Utilisez
jest.spyOn()lorsque vous devez vérifierconsole.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.

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
isOpendirectement 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.

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 :
- Job de test : Tests unitaires et de hooks rapides
- Job de build : Production build only after tests pass
- Job de packaging : Sync Capacitor, packaging Electron ou bundling d'artefacts
- 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.