Aller au contenu principal

Tests unitaires JavaScript : le guide complet 2026

Maîtrisez les tests unitaires JavaScript avec notre guide 2026. Au programme : Jest, Mocha, configuration, mocking, CI et conseils pour les apps Capacitor et Electron.

« Unit Tests JavaScript : guide complet 2026 » Capgo illustration du blog

Vous êtes probablement dans l’une de ces deux situations. Soit votre projet JavaScript compte très peu de tests et chaque refactorisation vous semble risquée, soit vous avez déjà des tests, mais la moitié d’entre eux sont lents, fragiles et difficiles à faire confiance.

Ça s’aggrave dans les applications Capacitor et Electron . Une simple fonctionnalité peut toucher à la logique métier partagée, aux API du navigateur, aux plugins natifs, aux fichiers locaux, à l’IPC et aux services distants dans un même flux. Si vous testez ces éléments de la mauvaise manière, votre suite de tests devient un labyrinthe de dépendances factices. Si vous les testez correctement, vous obtenez un retour rapide sur la logique qui casse.

Un bon travail de tests unitaires en JavaScript ne commence pas par une syntaxe de matcher astucieuse. Il commence par une frontière rigoureuse : tester directement la logique pure, isoler les effets de bord et éviter d’écrire des tests qui s’effondrent dès que vous renommez une fonction interne.

Table des matières

Choisir votre framework de test JavaScript

Un projet JavaScript professionnel a besoin d’un vrai test runner. Les scripts ad hoc et les vérifications manuelles dans la console ne passent pas à l’échelle dès que plusieurs développeurs travaillent sur la même base de code. Il vous faut la découverte des tests, des assertions, la gestion de l’asynchrone, des mocks et un moyen d’exécuter le tout de façon cohérente, en local comme en CI.

Les recommandations actuelles continuent de converger vers un petit ensemble d’options courantes. Jest, Mocha et Jasmine sont régulièrement mis en avant comme les frameworks principaux, avec Jest souvent mis en avant pour sa structure de tests intégrée, ses assertions, son mocking et sa prise en charge de l’asynchrone dans un seul package, comme le montre lab de test JavaScript sur Pluralsight.

Un tableau comparatif présentant les frameworks de test JavaScript populaires, notamment Jest, Mocha, Cypress et Playwright.

Pourquoi un framework est indispensable

La première erreur des équipes est de considérer les tests unitaires comme une activité secondaire. Cela conduit généralement à un nommage de fichiers incohérent, à des assertions personnalisées que personne ne retient et à des helpers que seule une personne comprend.

Un framework vous offre un langage commun :

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

Si votre équipe a aussi besoin d’une vue plus large de l’automatisation des tests au-delà du niveau unitaire, Capgo propose un aperçu utile de tests automatisés dans les workflows de livraison d’applications.

Jest vs Mocha en un coup d’œil

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

Jest est l’option tout-en-un. Elle intègre l’essentiel dont les équipes ont besoin dès le premier jour.
Mocha est plus modulaire. Il fournit un runner et vous laisse composer le reste de la stack.

Fonctionnalité Jest Mocha
Complexité de configuration Plus faible pour la plupart des équipes Plus élevé, car vous ajoutez généralement des bibliothèques d’assertion et de mocking
Assertions Intégré Généralement associé à une autre bibliothèque
Mocking Intégré Généralement associé à une autre bibliothèque
Tests asynchrones Intégré et simple d’utilisation Pris en charge, mais dépend surtout de l'environnement
Workflow de couverture Généralement intégré à la même toolchain Souvent composé d'éléments disparates
Le plus adapté Nouveaux projets, équipes qui veulent de la cohérence Stacks legacy, équipes qui veulent un contrôle modulaire

Règle pratique : Si votre équipe doit se demander quelles bibliothèques d'assertions et de mocking associer au runner, vous voulez probablement 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 bonnes 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 dont l'écosystème est déjà stabilisé. Mais pour un ingénieur de niveau intermédiaire qui configure une suite robuste de zéro, Jest réduit 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ésolvent un problème différent. Ils sont plus adaptés aux tests au niveau du navigateur et aux tests de bout en bout (E2E), et non à la boucle de développement rapide où doivent se situer les tests unitaires du code JavaScript.

Configuration du projet et premier test

Une configuration de test propre doit rester 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 dispose déjà d'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 complément 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 important, car le test-first modifie la façon dont vous concevez code. Les fonctions deviennent plus petites, les dépendances plus visibles, et les effets de bord cessent de contaminer 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 Le pattern garde les tests lisibles, même quand ils deviennent plus complexes.

  1. Préparer les entrées et la configuration nécessaire.
  2. Agir en appelant la fonction.
  3. Vérifier 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 décrire tout un workflow.

Pour les projets Capacitor et Electron, cette discipline compte d'autant plus que votre logique pure côtoie souvent du code d'intégration native ou desktop. Gardez vos règles métier testables sans le runtime de la plateforme : votre premier test ne sera pas votre dernier test utile.

Maîtriser les mocks et le Code asynchrone

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

C'est là que le mocking intervient. Il vous donne le contrôle de la frontière pour que le test se concentre sur la prise de décision de votre code.

Un schéma sur tableau blanc illustrant une architecture microservices avec des API, des stockages de données, des services externes et un flux de données événementiel.

Mockez les frontières, pas tout

Les principes de tests maintenables privilégient la couverture d'un seul comportement 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.

Mauvaise cible pour un test à base de 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

Meilleure cible :

  • 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 code Electron

Dans les applications mobiles et de bureau, je préfère une couche d’encapsulation autour des API natives ou de la plateforme. Les tests unitaires mockent alors cette couche, 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 modèle fonctionne aussi avec Electron. Encapsulez ipcRenderer, accès aux fichiers ou intégrations shell derrière un adaptateur fin. Les tests unitaires ciblent la couche de services, pas le runtime directement.

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 simulés.

Un exemple guidé peut aider si votre équipe harmonise encore le style de ses tests asynchrones :

Tester les flux asynchrones sans instabilité

Utiliser async/await dans les tests lorsque le code testé renvoie une promesse. C’est plus clair que les patterns à base de callbacks et plus facile à déboguer.

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 aussi le cas 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 scénario idéal que les cas d'erreur. En production, c'est souvent le scénario 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.

Utiliser 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, et les vérifications pre-commit doivent rester moins de 5 secondes, d’après cela Guide de test OpenReplay.

Je considère cela comme un outil de budgétisation, pas comme une religion. Si la majeure partie de vos efforts est consacrée aux tests de bout en bout, votre équipe attendra trop longtemps pour obtenir un retour. Si tout est limité aux tests unitaires, vous manquez les frontières réelles du système.

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 permissions, 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 tels que la connexion, le flux d’achat, la synchronisation ou les prompts de mise à jour

La couverture est une lampe torche, pas un objectif

Les rapports de couverture sont utiles lorsqu’ils aident à repérer des branches non testées dans une logique importante. Ils deviennent nuisibles lorsque les équipes visent des pourcentages de couverture pour le principe.

Un validateur de connexion avec des tests de cas limites bien pensés apporte plus de valeur qu’un fichier couvert 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 riche en validation, ce guide sur maîtriser la validation des formulaires front-end est un bon complément à la stratégie de tests unitaires.

Les tests orientés comportement survivent aux refactors

Une suite fiable doit vous permettre de refactoriser l’implémentation sans réécrire la moitié des tests. La façon la plus simple d’y parvenir est d’affirmer le comportement observable plutôt que les détails d’implémentation.

Cas d’usage qui résistent bien :

  • Conditions limites comme une entrée vide, des valeurs nulles, des types invalides et des chaînes trop longues
  • Résultats métier comme « renvoie refusé si la permission est manquante »
  • Transitions d’état comme « marque la mise à jour en attente après validation des métadonnées de téléchargement »

Cas d’usage qui se dégradent souvent :

  • vérifier les appels aux fonctions internes
  • affirmer l’ordre des méthodes privées
  • mocker chaque couche de la chaîne d’appels

Pour les équipes qui construisent des processus de release rigoureux, l’article de Capgo sur assurance qualité applicative est utile car il relie le travail de test à l’ensemble du pipeline de release.

Tester 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 le travail de tests unitaires JavaScript en 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 compte encore plus pour les projets Capacitor et Electron, où la dérive des environnements provoque des échecs subtils.

Faire 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 ensemble de modifications. Gardez la commande identique au développement local lorsque c’est possible.

Un workflow de base GitHub Actions peut se résumer à 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 en échec et les hypothèses de plateforme involontaires avant qu’ils ne rejoignent main.

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

Tester les interactions du plugin Capacitor

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

Le meilleur pattern est une fine 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 logique s’applique à l’accès à la caméra, aux prompts biométriques, à l’enregistrement des push tokens et à l’état du réseau. Gardez les appels de plugins dans des adaptateurs. Testez la logique applicative contre des interfaces que vous contrôlez.

Tester le renderer principal Electron et l'IPC code

Electron apps have two important seams: processus principal code et processus renderer code. Ne les confondez pas dans les 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 for menus, file operations, and app lifecycle decisions
  • Tests de contrat IPC pour la forme 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 qui compte. C'est le standard à viser pour vos code desktop et mobile.

Foire aux questions sur les tests unitaires JavaScript

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

Un test unitaire vérifie une petite partie de la logique de manière isolée. Un test d'intégration vérifie que quelques composants ou services fonctionnent correctement ensemble. Un test end-to-end exerce un parcours utilisateur dans 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 points de jonction comme le stockage, les wrappers de plugins et l'IPC. Utilisez les tests E2E avec parcimonie, pour les workflows dont la casse aurait un vrai impact.

Faut-il viser une couverture complète ?

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

La couverture est utile quand 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 de tests 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 bloquez 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 en ajoutant des tests aux modules que vous modifiez pendant vos features ou corrections de bugs
  • Extrayez la logique pure des fichiers difficiles à tester pour tester les règles métier sans bruit du framework ou du runtime
  • Ajoutez des wrappers de seam autour des plugins natifs, clients réseau, appels filesystem 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 il met en lumière le problème souvent négligé du sur-mocking et des tests fragiles qui en découlent

L’objectif n’est pas la complétude immédiate. C’est une amélioration continue là où les régressions coûtent le plus cher à l’équipe.


Si votre équipe déploie Capacitor ou Electron apps et a besoin d’un processus de release plus propre pour les modifications JavaScript, Capgo est une option à considérer. Il propose des live updates pour les applications CapacitorJS et Electron, avec des contrôles de déploiement progressif et de l'observabilité, pour que les équipes puissent associer des tests unitaires solides à un chemin plus sûr vers la publication des modifications du bundle web, sans attendre la revue du store pour chaque correctif.

Live updates pour les apps Capacitor

Lorsqu'un bug de la couche web est en ligne, 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 offre les meilleures informations pour créer une application mobile vraiment professionnelle.