Aller au contenu principal

Tests unitaires JavaScript : le guide complet 2026

Maîtrisez les tests unitaires JavaScript avec notre guide 2026. Inclut Jest, Mocha, la configuration, le mocking, la CI et des astuces pour Capacitor & Electron.

Tests unitaires JavaScript : le guide complet 2026

Vous êtes probablement dans l'une de ces deux situations : soit votre projet JavaScript n'a presque aucun test et chaque refactorisation semble risquée, soit vous avez déjà des tests, mais la moitié d'entre eux sont lents, fragiles et difficiles à faire confiance.

Cela s'aggrave dans les Capacitor et Electron applications. Une simple fonctionnalité peut impacter la logique métier partagée, les API du navigateur, les plugins natifs, les fichiers locaux, l'IPC et les services distants dans un même flux. Si vous testez ces éléments de la mauvaise manière, votre suite devient un labyrinthe de dépendances fictives. Si vous les testez correctement, vous obtenez un feedback rapide sur la logique qui pose problème.

De bons tests unitaires JavaScript ne commencent pas par une syntaxe de matcher complexe. Cela commence par une délimitation rigoureuse : testez la logique pure directement, isolez les effets de bord et évitez d'écrire des tests qui s'effondrent dès que vous renommez une fonction interne.

Table of Contents

Choisir votre framework de test JavaScript

Un projet JavaScript professionnel nécessite un véritable test runner. Les scripts ad hoc et les vérifications manuelles dans la console ne sont pas scalables dès que plusieurs ingénieurs touchent à la même base de code. Vous avez besoin de la découverte de tests, d'assertions, de la gestion de l'asynchrone, de mocks et d'un moyen d'exécuter le tout de manière cohérente en développement local et en CI.

Les recommandations actuelles convergent vers un petit groupe d'options grand public. Jest, Mocha et Jasmine sont régulièrement cités comme les principaux frameworks, avec Jest souvent mis en avant pour sa structure de test intégrée, ses assertions, son mocking et son support de l'asynchrone dans un seul package, comme illustré ici lab de test JavaScript Pluralsight.

Un tableau comparatif des frameworks de test JavaScript populaires, incluant Jest, Mocha, Cypress et Playwright.

Pourquoi un framework n'est pas optionnel

La première erreur des équipes est de traiter les tests unitaires comme une activité secondaire. Cela mène généralement à des noms de fichiers incohérents, des assertions personnalisées que plus personne ne comprend et des helpers que seule une personne maîtrise.

Un framework vous offre un langage commun :

  • Structure des tests avec describe et test ou it
  • Assertions avec des matchers lisibles
  • Hooks pour le setup et le teardown
  • Support de l'asynchrone pour les promises et les timers
  • Outils de mocking pour les dépendances externes

Si votre équipe a également besoin d'une vision plus large de l'automatisation des tests au-delà de l'unité, Capgo propose un aperçu utile de les tests automatisés dans les workflows de déploiement d'apps.

Jest vs Mocha en un coup d'œil

Jest et Mocha représentent deux philosophies différentes.

Jest est l'option tout-en-un. Il inclut la plupart des outils dont les équipes ont besoin dès le premier jour.
Mocha est plus modulaire. Il vous fournit un runner et vous laisse assembler le reste de la stack.

Fonctionnalité Jest Mocha
Complexité de configuration Plus faible pour la plupart des équipes Plus élevée car vous ajoutez généralement des bibliothèques d'assertions et de mocking
Assertions Intégré Généralement couplé à une autre bibliothèque
Mocking Intégré Généralement couplé à une autre bibliothèque
Tests asynchrones Intégré et simple d'utilisation Supporté, mais dépend de la configuration globale
Workflow de couverture Généralement intégré à la même toolchain Souvent composé d'éléments disparates
Meilleure option Nouveaux projets, équipes cherchant la cohérence Stacks legacy, équipes cherchant un contrôle modulaire

Règle pratique : Si votre équipe doit choisir quelle bibliothèque d'assertion et de mocking coupler au runner, vous devriez probablement choisir Jest.

Ce que je recommande pour la plupart des équipes

Pour la plupart des projets modernes, je choisirais Jest à moins que la base de code n'ait déjà de solides raisons de rester sur Mocha. Cette recommandation se renforce encore lorsque l'application inclut Capacitor ou Electron, car ces projets comportent déjà suffisamment d'éléments mobiles. Réduire la prolifération des outils de test est rapidement rentable.

Mocha reste pertinent pour les anciens services Node.js ou les bases de code pérennes où l'écosystème est déjà stabilisé. Mais pour un ingénieur de niveau intermédiaire qui met en place une suite robuste de zéro, Jest supprime généralement plus de frictions qu'il n'en crée.

Une précision importante sur le périmètre. Cypress et Playwright sont d'excellents outils, mais ils répondent à un autre problème. Ils sont plus adaptés aux tests au niveau du navigateur et aux tests de bout en bout (E2E), pas à la boucle de feedback rapide où doivent vivre les tests unitaires du code JavaScript.

Configuration du projet et premier test

Une configuration de test propre doit être banale. Si l'ajout du premier test semble compliqué, la suite ne restera probablement pas saine.

Un homme portant des lunettes travaille sur un projet de programmation sur un ordinateur portable à un bureau en bois.

Une configuration Jest simple

Commencez par un projet JavaScript qui possède déjà un package.json. Ajoutez ensuite Jest comme dépendance de développement et configurez un script de test.

{
  "scripts": {
    "test": "jest"
  }
}

Cela suffit pour de nombreux projets. Vous pourrez ajouter d'autres configurations plus tard si votre système de modules, votre transpilation ou votre structure monorepo l'exige.

Si vous développez une application Capacitor en local et que vous souhaitez préparer votre environnement de dev avant d'ajouter des tests sur votre logique partagée, le guide de Capgo sur configurer un environnement local Capacitor est un compagnon pratique.

Écrivez le test avant le code

Le pattern test-first n'est pas qu'une simple préférence personnelle. Les directives JavaScript du Consumer Financial Protection Bureau des États-Unis recommandent explicitement de écrire le test en premier, d'organiser les tests avec describe et it, et de structurer les vérifications autour des expect(...) assertions dans ses Guide des tests unitaires JavaScript.

C'est crucial car l'approche test-first modifie votre conception de code. Les fonctions ont tendance à devenir plus petites, les dépendances plus visibles, et les effets de bord cessent de polluer la logique qui devrait rester pure.

Voici un exemple minimal :

// math.js
function addTax(amount, rate) {
  return amount + amount * rate;
}

module.exports = { addTax };
// math.test.js
const { addTax } = require('./math');

describe('addTax', () => {
  it('returns the amount with the tax applied', () => {
    expect(addTax(100, 0.2)).toBe(120);
  });
});

Utilisez Arrange Act Assert à chaque fois

Le Arrange, Act, Assert permet de garder les tests lisibles, même lorsqu'ils deviennent complexes.

  1. Arrange les entrées et toute la configuration nécessaire.
  2. Act en appelant la fonction.
  3. Assert sur le résultat.

Appliqué à un helper de validation :

function isSupportedPlatform(platform) {
  return ['ios', 'android', 'web', 'desktop'].includes(platform);
}

describe('isSupportedPlatform', () => {
  it('returns true for ios', () => {
    // Arrange
    const platform = 'ios';

    // Act
    const result = isSupportedPlatform(platform);

    // Assert
    expect(result).toBe(true);
  });
});

Les tests courts vieillissent bien. Un test doit généralement répondre à une seule question, pas narrer tout un workflow.

Pour les projets Capacitor et Electron, cette discipline est d'autant plus cruciale que votre logique pure est souvent couplée à des code natifs ou desktop. Gardez la règle métier testable sans le runtime de la plateforme, et votre premier test ne sera pas le dernier utile.

Maîtriser les mocks et l'asynchronisme Code

La plupart des bugs dans les code d'application ne viennent pas de l'addition de deux nombres. Ils proviennent de code qui sortent de leur propre périmètre : requêtes réseau, fichiers, API de plugins, timers, canaux IPC, couches de stockage.

C'est là que le mocking intervient. Il vous permet de contrôler la frontière pour que le test puisse se concentrer sur la logique de décision de votre code.

Un schéma sur tableau blanc illustrant une architecture microservices avec des API, des bases de données, des services externes et un flux de données piloté par les événements.

Mockez les frontières, pas tout

Maintainable test guidance emphasizes la couverture d'un comportement unique et une assertion forte par test, et il prévient également que l'abus de mocks rend les tests fragiles et trop dépendants des détails d'implémentation, comme résumé dans cet article TestRail sur les tests unitaires maintenables.

Cet avertissement est crucial en JavaScript. Les équipes commencent souvent par mocker chaque module importé et finissent par tester si les fonctions appellent d'autres fonctions dans le « bon » ordre, au lieu de tester le comportement réel.

Mauvais sujet pour un test riche en mocks :

  • si l'helper A a appelé l'helper B
  • si le service C a appelé le serializer D
  • si une fonction privée interne a été exécutée deux fois

Meilleur sujet :

  • ce que la fonction a retourné
  • si elle a géré correctement l'échec d'une dépendance
  • si elle a transformé les données selon le format attendu

Un meilleur pattern pour Capacitor et Electron code

Dans les applications mobiles et desktop, je préfère utiliser une couche wrapper autour des API natives ou de la plateforme. Ainsi, les tests unitaires mockent le wrapper, et non la plateforme elle-même.

Structure d'exemple :

// cameraGateway.js
async function getPhoto(cameraPlugin) {
  return cameraPlugin.getPhoto();
}

module.exports = { getPhoto };
// profilePhotoService.js
async function loadProfilePhoto(cameraGateway) {
  const photo = await cameraGateway.getPhoto();
  return { path: photo.path, ready: true };
}

module.exports = { loadProfilePhoto };
// profilePhotoService.test.js
const { loadProfilePhoto } = require('./profilePhotoService');

test('returns mapped photo data', async () => {
  const fakeCameraGateway = {
    getPhoto: jest.fn().mockResolvedValue({ path: '/tmp/pic.jpg' })
  };

  const result = await loadProfilePhoto(fakeCameraGateway);

  expect(result).toEqual({ path: '/tmp/pic.jpg', ready: true });
});

Ce pattern fonctionne aussi pour Electron. Envelopper ipcRenderer, l'accès aux fichiers ou les intégrations shell derrière un adaptateur léger. Les tests unitaires ciblent la couche service, pas directement le runtime.

Pour les équipes qui testent la logique de release et les chemins de mise à jour dans les apps Capacitor, Capgo propose un guide pertinent sur tester les mises à jour OTA Capacitor avec des scénarios mockés.

Un tour d'horizon rapide peut aider si votre équipe doit encore harmoniser son style de tests asynchrones :

Tester les flux async sans flakiness

Utilisez async/await dans les tests lorsque le code testé retourne une promesse. C'est plus clair que les patterns lourds en callbacks et plus facile à débugger.

async function fetchProfile(api) {
  const response = await api.getUser();
  return response.name;
}

test('returns the user name from the API response', async () => {
  const api = {
    getUser: jest.fn().mockResolvedValue({ name: 'Ava' })
  };

  const result = await fetchProfile(api);

  expect(result).toBe('Ava');
});

Testez également le chemin d'échec :

test('throws when the API request fails', async () => {
  const api = {
    getUser: jest.fn().mockRejectedValue(new Error('network failed'))
  };

  await expect(fetchProfile(api)).rejects.toThrow('network failed');
});

Testez aussi bien le chemin idéal que les cas d'erreur. En production, c'est souvent le cas d'erreur dont les utilisateurs se souviennent.

Stratégies avancées pour des tests robustes

Une suite de tests devient utile lorsqu'elle le reste après les changements de code. C'est plus difficile que d'écrire une pile de tests qui passent.

Un diagramme illustrant les stratégies pour créer des logiciels robustes grâce à une couverture de tests complète et des suites de tests maintenables.

Utilisez la répartition des tests comme un budget

Un guide pratique recommande une 70/20/10 répartition entre tests unitaires, d'intégration et de bout en bout, les tests unitaires offrant le feedback le plus rapide et les échecs les plus stables. Ce même guide précise qu'une suite unitaire complète devrait idéalement se terminer en moins de 10 secondes, and pre-commit checks should stay sous les 5 secondes, according to this Guide de test OpenReplay.

I treat that as a budgeting tool, not a religion. If most of your effort goes into end-to-end tests, your team will wait too long for feedback. If everything is unit-only, you’ll miss real system boundaries.

Pour une application Capacitor ou Electron, un équilibre sain ressemble généralement à ceci :

  • Tests unitaires pour la logique de tarification, les règles de permission, la sérialisation, l'éligibilité aux mises à jour, les feature flags et les transformations d'état
  • Tests d'intégration pour les adaptateurs de stockage, les wrappers de plugins et les contrats IPC
  • Tests E2E pour quelques parcours critiques comme la connexion, le flux d'achat, la synchronisation ou les prompts de mise à jour

La couverture est une lampe torche, pas une cible

Les rapports de couverture sont utiles lorsqu'ils vous aident à repérer des branches non testées dans une logique importante. Ils deviennent nuisibles lorsque les équipes cherchent à atteindre des pourcentages de couverture pour la forme.

Un validateur de connexion avec des tests de cas limites bien pensés apporte plus de valeur qu'un fichier à couverture élevée rempli d'assertions triviales. C'est particulièrement vrai pour les code à forte saisie, comme les formulaires, les parseurs, la logique de dates et les vérifications de permissions. Si votre équipe renforce la qualité autour d'une UI à forte validation, ce guide sur la maîtrise de la validation de formulaires frontend est un bon complément à une stratégie de tests unitaires.

Les tests orientés comportement survivent au refactoring

Une suite fiable doit vous permettre de refactoriser le code interne sans réécrire la moitié des tests. Le moyen le plus simple d'y parvenir est de vérifier le comportement observable plutôt que les détails d'implémentation.

Cas d'usage pertinents :

  • Conditions limites comme les entrées vides, les valeurs nulles, les types invalides et les chaînes trop longues
  • Résultats métier tels que « accès refusé pour permission manquante »
  • Transitions d'état comme « marque la mise à jour comme en attente après la validation des métadonnées du téléchargement »

Cas d'usage qui finissent souvent par devenir obsolètes :

  • inspecter les appels de fonctions utilitaires internes
  • vérifier l'ordre d'exécution des méthodes privées
  • mocker chaque couche de la chaîne d'appels

Pour les équipes d'app qui construisent des processus de release disciplinés, l'article de Capgo sur l'assurance qualité des apps est utile car il relie le travail de test au pipeline de release global.

Tester pour la CI, Capacitor et les apps Electron

Un test qui ne s'exécute que sur la machine d'un développeur n'est pas un filet de sécurité. C'est une habitude locale.

La CI transforme les tests unitaires JavaScript en une infrastructure d'équipe. Chaque push, pull request ou branche de release peut exécuter les mêmes commandes avec les mêmes attentes. Cette cohérence est encore plus cruciale pour Capacitor et les projets Electron, où la dérive d'environnement cause des échecs subtils.

Faites de la CI le chemin d'exécution par défaut

Au minimum, votre CI doit installer les dépendances et exécuter la suite de tests unitaires à chaque modification. Gardez la commande identique au développement local quand c'est possible.

Un workflow GitHub Actions de base peut être aussi simple que ceci :

name: test

on: [push, pull_request]

jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test

C'est suffisant pour détecter les imports cassés, les assertions échouées et les suppositions de plateforme accidentelles avant qu'ils n'atteignent la branche main.

Pour les équipes mobiles qui déploient via des pipelines automatisés, Capgo propose un guide pratique pour configurer la CI/CD pour les apps Capacitor.

Tester les interactions du plugin Capacitor

La mauvaise façon de tester unitairement les code de Capacitor est d'importer les plugins natifs directement dans chaque service. Cela couple votre suite de tests au bridge de la plateforme.

Le pattern à privilégier est une fine couche d'abstraction :

// deviceStorage.js
async function saveFile(filesystem, path, data) {
  return filesystem.writeFile({ path, data });
}

module.exports = { saveFile };
// draftService.js
async function persistDraft(storage, draft) {
  await storage.save('draft.json', JSON.stringify(draft));
  return { saved: true };
}

module.exports = { persistDraft };
// draftService.test.js
const { persistDraft } = require('./draftService');

test('persists a serialized draft', async () => {
  const storage = {
    save: jest.fn().mockResolvedValue(undefined)
  };

  const result = await persistDraft(storage, { title: 'Hello' });

  expect(result).toEqual({ saved: true });
});

La même idée s'applique à l'accès à la caméra, aux prompts biométriques, à l'enregistrement du token push et au statut réseau. Gardez les appels aux plugins dans des adapters. Testez la logique de l'app via des interfaces que vous contrôlez.

Tester le main renderer et l'IPC code d'Electron

Electron apps have two important seams: processus principal code et processus renderer code. Ne les masquez pas dans vos tests.

Une configuration fiable sépare généralement :

  • Tests unitaires du renderer pour les view models, l'état, le formatage et la logique métier côté UI
  • Tests unitaires du processus principal pour les menus, les opérations de fichiers et le cycle de vie de l'app
  • Tests de contrat IPC pour la structure des messages et les réponses attendues

Exemple de wrapper IPC :

// ipcGateway.js
function sendSettings(ipcRenderer, payload) {
  ipcRenderer.send('settings:update', payload);
}

module.exports = { sendSettings };
// ipcGateway.test.js
const { sendSettings } = require('./ipcGateway');

test('sends settings update over ipc', () => {
  const ipcRenderer = { send: jest.fn() };

  sendSettings(ipcRenderer, { theme: 'dark' });

  expect(ipcRenderer.send).toHaveBeenCalledWith('settings:update', { theme: 'dark' });
});

Si vous changez plus tard l'implémentation interne d'un helper pour un autre, ce test reste valide car il vérifie le comportement essentiel. C'est la norme à adopter sur desktop et mobile avec code.

Foire aux questions sur les tests unitaires JavaScript

Quelle est la différence entre les tests unitaires, d’intégration et E2E ?

R test unitaire vérifie une petite portion de logique de manière isolée. Un test d'intégration vérifie si quelques composants ou services fonctionnent correctement ensemble. Un test de bout en bout simule un parcours utilisateur via l'application en cours d'exécution.

Utilisez les tests unitaires pour valider rapidement vos règles métier. Utilisez les tests d'intégration pour les interfaces comme le stockage, les wrappers de plugins et l'IPC. Utilisez les tests E2E avec parcimonie pour les workflows critiques.

Faut-il viser une couverture complète ?

Non. Une couverture totale peut pousser les équipes vers des tests sans valeur.

La couverture est utile lorsqu'elle révèle des code risqués que personne n'a testés. Elle ne l'est pas quand les ingénieurs ajoutent des assertions superficielles juste pour satisfaire un dashboard. Si votre suite est fragile, plus de couverture ne la sauvera pas.

Comment ajouter des tests à une base de code existante

Commencez là où les changements ont déjà lieu. Ne figez pas l'équipe en annonçant une réécriture géante de la stratégie de test.

Une séquence pratique ressemble à ceci :

  • Protégez d'abord les code actifs by adding tests to modules you touch during feature work or bug fixes
  • Extrayez la logique pure des fichiers difficiles à tester pour que les règles métier puissent être testées sans le bruit du framework ou du runtime
  • Ajoutez des wrappers de jointure (seams) autour des plugins natifs, des clients réseau, des appels au système de fichiers et de l'IPC Electron
  • Refusez les patterns fragiles lors de l'introduction de mocks. Les conseils de bonnes pratiques de test JavaScript est particulièrement utile ici, car elle met en lumière le problème souvent négligé du sur-mocking et la fragilité des tests qui en découle

L'objectif n'est pas l'exhaustivité immédiate, mais une amélioration constante là où les régressions coûtent le plus cher à l'équipe.


Si votre équipe déploie des Capacitor ou Electron apps et nécessite un processus de release plus propre pour les changements JavaScript, Capgo est une option à envisager. Elle fournit des live updates pour les apps CapacitorJS et Electron, avec des contrôles de déploiement progressif et de l'observabilité, pour que les équipes associent de solides tests unitaires à un chemin plus sûr vers le déploiement des changements de bundle web, sans attendre la validation des stores pour chaque correctif.

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.