Vous pushez une petite modification d’interface avant la pause déjeuner. Elle semble anodine. Le label d’un bouton change, un rendu conditionnel est simplifié, et un hook utilitaire se voit ajouter 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. Le shell 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 remarqué parce que code avait des tests, mais pas les bons, et surtout pas un système fiable autour de ces tests.
C’est le principal problème du unit testing React dans les équipes en production. Écrire quelques tests qui passent n’est pas difficile. Construire une suite qui vous protège encore pendant les refactors, les trains de release, les hotfixes et le packaging multiplateforme, c’est là que ça se complique. Les applications React ne tombent pas parce qu’une équipe a oublié comment appeler render(). Ils échouent parce que les tests dérivent vers les détails d’implémentation, que le comportement asynchrone est masqué, et que la CI traite le test comme une case à cocher plutôt que comme une porte de release.
Les tests unitaires modernes de React fonctionnent comme un système de sécurité. Un feedback rapide en local. Des vérifications déterministes en CI. Des limites claires entre ce qui a sa place dans un test unitaire et ce qui n'en a pas. C'est d'autant plus important quand le même code React est livré via des navigateurs, des conteneurs Capacitor ou des shells Electron.
Table des matières
- Pourquoi les tests unitaires React sont votre filet de sécurité
- 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
Pourquoi les tests unitaires React sont votre filet de sécurité
Les tests unitaires valent le coup quand ils attrapent l'erreur que vous étiez sûr de ne jamais voir arriver. En React, cela veut souvent dire qu'un composant s'affiche toujours, mais que le comportement dont dépend l'utilisateur a changé. Un bouton désactivé devient cliquable. Un état de chargement ne disparaît jamais. Un message de secours disparaît après un refactoring. Ces échecs sont mineurs en code mais coûtent cher en production.
Les tests React ont changé de façon importante quand React Testing Library est devenue le modèle de référence pour tester le comportement plutôt que l'implémentation interne, poussant les équipes vers des tests qui reflètent le comportement de l'utilisateur plutôt que les props ou l'état des composants, comme le montre le guide de test de React Native à Aperçu des tests React Native. Ce changement compte, car le code React est constamment réorganisé. Les hooks bougent. Les composants se scindent. 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, libellé, état ou repli ?
- Comportement à l’interaction : Un clic, une saisie ou un basculement modifie-t-il correctement l’interface ?
- Gestion des limites : Le composant se comporte-t-il correctement avec des entrées attendues, des données manquantes ou un chemin d’erreur ?
Un test faible protège la mauvaise chose :
- Internes du composant : Forme de l’état, méthodes privées, props réservées à l’implémentation
- Mécanismes du framework : Si React a mis à jour un hook exactement comme vous l’attendiez en interne
- Détails de l'enfant : 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 à 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 de tests automatisés pour les apps en production.
Pour les équipes React qui déploient souvent, la confiance vient de cette répartition des tâches. Les tests unitaires détectent rapidement les régressions locales. Les tests d'intégration vérifient les points de jonction. Les tests de bout en bout confirment les parcours critiques. Si vous sautez la couche unitaire, tout ce qui est plus lent en aval doit 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 accusent 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 : rendre l'environnement ennuyeux. L'ennui est une qualité ici.

Commencez par un runner et un environnement prévisibles
Pour une app React moderne, en particulier une app créée avec Vite, la configuration de base doit inclure :
- Un test runner : Jest reste courant, notamment dans les bases de code React plus anciennes et les stacks CI d’entreprise.
- Un environnement proche du navigateur :
jsdompermet aux tests de composants de rendre la sortie DOM. - Les utilitaires de Testing Library :
@testing-library/reactet@testing-library/jest-dom - Un point d’entrée unique de configuration : Un seul fichier pour enregistrer les matchers et les mocks globaux
Le workflow clé que les recommandations de test React mettent en avant 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 et vérifier le changement du DOM, comme décrit dans la documentation de test React. Ce 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 setup 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é.
Gardez le comportement local et CI aligné
La commande locale doit correspondre le plus possible à la commande CI. Si les développeurs exécutent le mode watch avec des valeurs par défaut permissives, mais que le CI utilise une configuration plus stricte, des échecs inattendus apparaîtront après le merge. Gardez les scripts explicites :
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:ci": "jest --runInBand --coverage"
}
}
Un court tutoriel aide les nouveaux membres de l’équipe à atteindre rapidement le même niveau de base :
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 lorsque c’est possible. Moins chaque test individuel a besoin de comportement personnalisé, plus votre système devient fiable.
Écrire des tests de composants pertinents
Les organisations n’ont pas de mal à écrire des tests. Elles ont du mal à écrire des tests qui comptent encore six mois plus tard.
Le modèle standard pour tester unitairement des composants React reste le bon : rendre le composant, interroger l’interface avec des sélecteurs centrés sur l’utilisateur, déclencher une interaction et vérifier le changement de DOM résultant, qui permet d’éviter les détails d’implémentation comme l’état ou les props, comme décrit dans guide de test React. L’astuce est d’appliquer ce modèl’avec retenue.
Testez l'accordéon comme l'utiliserait un utilisateur
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é.
Ce comportement suffit pour 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-controls, ou 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 le mauvais style 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 que le contenu caché n'existe pas avant l'interaction |
findBy |
Se résout quand l'élément apparaît | Est rejeté après l'attente | Vérifier que le contenu chargé en asynchrone apparaît après un fetch ou une mise à jour différée |
Un modèle mental simple peut aider :
- Utiliser
getBypour les éléments qui doivent déjà exister. - Utiliser
queryBypour les éléments qui ne doivent pas encore exister. - Utiliser
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 quand le composant se met à jour. Cette incertitude se transforme ensuite en instabilité.
Un exemple pratique d'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>
);
}
Et voici à quoi ressemblent les tests à conserver :
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 compte tout autant. Aucune assertion sur l’état interne. Aucune vérification que setOpen a été appelé. Aucune capture de l’arbre rendu complet. Ces tests n’apporteraient que de la maintenance, pas de la confiance.
Quelques habitudes renforcent les tests de composants :
- Privilégiez les requêtes par rôle : Boutons, titres, dialogues, alertes et champs de saisie doivent généralement être ciblés par leur rôle.
- Gardez chaque test ciblé : Un seul comportement visible par test rend les échecs lisibles.
- Nommez les tests d’après les résultats : « 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 design. Peut-être qu’il masque l’état au mauvais endroit. Peut-être 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 une grande partie de leur logique en dehors des composants. Les transitions d'état se trouvent dans les hooks. La validation et le formatage sont gérés par des fonctions utilitaires. Le façonnage des données a souvent lieu avant tout rendu. Si vous ne testez que les composants visibles, vous passerez à côté d'une part importante du code qui peut encore 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 enveloppez les appels modifiant l'état dans 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 centré 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 entrailles de React. Vous vérifiez le comportement externe du hook.
Pour les équipes produit qui créent des primitives UI ou des briques de fonctionnalités réutilisables, ce pattern compte beaucoup. Les hooks deviennent souvent l’interface partagée entre applications, design systems ou outils internes. Si vous concevez des comportements réutilisables à visée commerciale, les ressources sur les hooks pour les produits des makers peuvent aider à considérer les hooks comme des briques produit 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 jsdom React, ni de Testing Library. Si une fonction est pure, testez-l’avec Jest pur dans un environnement Node.
Exemple :
export function formatDisplayName(firstName: string, lastName: string) {
return `${firstName.trim()} ${lastName.trim()}`.trim();
}
Ce test doit être ultra 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’avantage ici, c’est la vitesse et la clarté. Quand une fonction n’a pas besoin d’un arbre de rendu, ne lui en donnez pas un. Les outils spécifiques à React ajoutent de la surcharge. Gardez les tests de logique métier petits, rapides et proches de la fonction qu’ils vérifient.
Une répartition pratique fonctionne bien :
- Hooks : Utiliser
renderHook,act(), et des providers wrapper si nécessaire. - Utilitaires : Utilisez Jest pur, sans DOM.
- Logique transversale avec état : Extrayez-la dans des helpers testables quand le test du composant commence à en faire trop.
Les équipes surchargent souvent les tests de composants avec des assertions de logique 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 de tests React peu fiables cassent à deux endroits : aux frontières des dépendances et sur la gestion du temps.
C'est pourquoi les tests asynchrones et le mocking font la différence entre une suite de tests jouet et une suite fiable avant une release. Une analyse attribue 46,5 % de l'instabilité des tests (flakiness) à 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.

Mockez la frontière, pas chaque couche
La façon la plus rapide d'écrire un test trompeur est de mocker la moitié de l'arbre de vos composants, puis d'affirmer que vos propres mocks fonctionnent.
Pour un composant qui récupère les données d’un compte, mockez le client réseau ou le module API. Ne mockez pas le hook, le composant de ligne enfant, le spinner de chargement et les trois fonctions utilitaires, sauf si le test a vraiment besoin d’isoler ces frontières.
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 éléments internes par défaut : hooks personnalisés, enfants simples, utilitaires locaux
Si un test passe parce que toutes les parties difficiles ont été remplacées par des fakes, il ne vous apporte pas beaucoup de confiance pour votre release.
Pour les équipes qui recherchent 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 effectue l'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, lorsque vous devez attendre une condition spécifique :
await waitFor(() => {
expect(screen.getByRole('alert')).toBeInTheDocument();
});
Utiliser findBy quand l'apparition d'un élément est l'événement qui vous importe. 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 également que vous respectiez act() la sémantique autour des mises à jour. Testing Library gère une grande partie de cela pour vous, mais si vous pilotez l'état manuellement ou avancez les timers, vous devez quand même 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 | Usage recommandé | Erreur courante |
|---|---|---|
jest.fn() |
Callbacks factices autonomes ou fonctions injectées | L'utiliser pour remplacer tout un module alors qu'un simple callback suffit |
jest.spyOn() |
Observer ou redéfinir 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'import | Mocker de gros modules par défaut et perdre des comportements significatifs |
Les exemples aident :
- Recourez à
jest.fn()lorsqu'un composant reçoit uneonSubmitprop. - Utiliser
jest.spyOn()lorsque vous devez vérifierconsole.error, une méthode de stockage, ou un appel API exporté. - Utiliser
jest.mock()lorsqu'importer un module déclencherait sinon des I/O, 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 (fallback) asynchrones méritent des tests à part entière, 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 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 qui comptent. 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 question : quels chemins 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 passe-plat 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 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 refactor.
- 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
Beaucoup de guides React ne consacrent pas assez de temps à l’omission. Ce manque compte, car le sur-mocking et les tests de détails d’implémentation créent des suites fragiles qui passent alors que l’expérience utilisateur casse encore, comme le souligne le guide de BrowserStack sur ce qu’il ne faut pas tester en unitaire dans React.
Évitez ou limitez fortement ces patterns :
- Assertions sur l’état interne : Ne testez pas
isOpendirectement alors que vous pouvez vérifier que le panneau s’est ouvert. - Comportement du framework : Ne testez pas que React a appelé un effet. Testez le résultat de la modification apportée par l’effet.
- Fonctionnement interne des bibliothèques tierces : Testez votre intégration avec un sélecteur de date ou un routeur, pas la logique de rendu de la bibliothèque.
- Unités trop fragmentées : Si vous avez mocké chaque composant enfant et chaque helper, vous ne testez peut-être plus un comportement significatif.
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 : la responsabilité aux frontières. Testez ce qui relève de votre code. Ne testez pas ce que React, le navigateur ou une bibliothèque mature gère déjà, sauf si votre couche d'intégration modifie le contrat.
Quand les snapshots aident et quand ils nuisent
Les snapshots ne sont pas inutiles. Ils sont juste faciles à mal utiliser.
Utilisez-les avec parcimonie pour les composants au rendu stable et simple, là où un diff structurel étendu 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 finissent par les mettre à jour par réflexe.
De meilleures alternatives 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 qui compte.
- 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 seul système. C'est ce changement de mentalité qui améliore le plus vite la qualité des tests. Arrêtez de demander 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 tourne que sur la machine 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 beaucoup d'équipes laissent encore subsister des lacunes critiques. Les tests sont lancés à la main. 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 se transforment en échecs de release plus importants.

Une pull request doit déclencher le même contrôle bloquant à 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 depuis le lockfile
- Utiliser la même commande de test à chaque fois
- Stopper au premier échec de test
- Publier les artefacts seulement une fois les tests passés
C’est le cœur des pratiques de déploiement continu pour les équipes d’apps. Gagner 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 voulu. Les pipelines les plus solides sont souvent les moins surprenants.
Pourquoi c'est plus important pour Capacitor et Electron
Les applications React multiplateformes présentent un risque de release plus élevé que les applications web, car la même interface code est souvent déployée dans des conteneurs différents, avec des hypothèses d’exécution distinctes.
Quelques exemples montrent où les pipelines sont utiles :
- Apps Capacitor : Les code web peuvent 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 modifie le comportement après packaging.
- Apps Electron : Un composant renderer peut dépendre d’APIs preload, de messages window ou d’états spécifiques au desktop, qui n’existeront pas dans un test navigateur standard s’ils ne sont pas explicitement mockés.
- Trains de release partagés : Un mauvais bundle peut impacter plusieurs cibles si votre processus de déploiement ne contrôle pas strictement la publication.
C’est pourquoi les tests unitaires doivent s’exécuter avant les jobs de packaging, et les jobs de packaging avant les jobs de distribution. Chaque étape réduit le risque. Les tests unitaires détectent rapidement les régressions locales. Le packaging par plateforme valide les hypothèses d’environnement. L’approbation manuelle ou le déploiement progressif gère la confiance finale pour la release.
Un workflow GitHub Actions concret
Un pipeline plus mature répartit généralement les responsabilités :
- Job de test : Tests unitaires et de hooks rapides
- Job de build : Build de production uniquement après le passage des tests
- Job de packaging : Synchronisation Capacitor, packaging Electron ou regroupement d’artefacts
- Job de release : Publication 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 comptent. Une option dans ce workflow est Capgo, qui publie des bundles web signés pour les applications CapacitorJS et Electron, avec prise en charge du rollback et des contrôles de déploiement progressif par canal. En pratique, cela signifie que votre job de test React peut servir de première barrière stricte avant qu’un bundle web ne soit promu vers la livraison en staging ou en production.
La règle opérationnelle est simple. Ne laissez pas l’infrastructure de release compenser des tests fragiles. Utilisez l’infrastructure de release après que des tests fiables ont déjà filtré les mauvaises modifications.
Un système de tests fiable change le comportement de l’équipe. Les ingénieurs fusionnent avec moins d’hésitation. Les reviewers se concentrent sur les cas limites au lieu de relancer manuellement les tests de base. Les responsables de release 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 React via Capacitor ou Electron, la sécurité des releases dépend de plus que des tests locaux verts. Capgo offre aux équipes un moyen contrôlé de publier des mises à jour web signées, de cibler des canaux de déploiement progressif et de faire un rollback des bundles défectueux sans attendre la revue du store, ce qui s’intègre naturellement derrière un pipeline CI qui exige déjà des tests unitaires réussis avant le déploiement.