Guide complet : Tester vos composants Vue.js avec Mocha et Chai

Decouvrez comment ecrire des tests unitaires pour Vue.js avec Mocha et Chai. Testez formulaires, evenements et interactions utilisateur.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 11 min read
Guide complet : Tester vos composants Vue.js avec Mocha et Chai
Table of Contents

Introduction aux Tests Unitaires Vue.js

Les tests unitaires constituent un pilier fondamental du developpement logiciel professionnel. Dans l’ecosysteme Vue.js, ils permettent de verifier que chaque composant fonctionne correctement de maniere isolee, independamment du reste de l’application. Cette approche garantit la fiabilite du code et facilite grandement la maintenance a long terme.

Pourquoi tester vos composants Vue.js ?

L’investissement dans les tests unitaires apporte des benefices considerables :

  • Detection precoce des bugs : Les erreurs sont identifiees avant la mise en production
  • Documentation vivante : Les tests servent de specification executable du comportement attendu
  • Refactoring en confiance : Modifier le code devient moins risque grace au filet de securite
  • Collaboration facilitee : Les nouveaux developpeurs comprennent rapidement le fonctionnement
  • Qualite accrue : Le code teste est generalement mieux structure et plus modulaire

Mocha et Chai : Un duo puissant

Mocha est un framework de test JavaScript flexible qui fournit la structure pour organiser et executer vos tests. Il offre une syntaxe claire avec describe() pour grouper les tests et it() pour definir les cas de test individuels.

Chai est une bibliotheque d’assertions qui s’integre parfaitement avec Mocha. Elle propose trois styles d’assertions : expect, should et assert, vous permettant de choisir celui qui correspond le mieux a votre preference.

Ensemble, ils forment une solution robuste et eprouvee pour tester les applications Vue.js.


Installation et Configuration

Pre-requis

Avant de commencer, assurez-vous d’avoir :

  • Node.js (version 14 ou superieure)
  • npm ou yarn
  • Un projet Vue.js existant

Installation des dependances

Pour configurer Mocha et Chai dans votre projet Vue.js, installez les packages necessaires :

# Installation des dependances de test
npm install --save-dev mocha chai @vue/test-utils

# Pour le support de Vue 3
npm install --save-dev @vue/test-utils@next

# Utilitaires supplementaires
npm install --save-dev jsdom jsdom-global
npm install --save-dev @babel/register @babel/preset-env

Configuration de Mocha

Creez un fichier .mocharc.js a la racine de votre projet :

// .mocharc.js
module.exports = {
  require: [
    '@babel/register',
    'jsdom-global/register'
  ],
  extension: ['js', 'vue'],
  spec: 'tests/**/*.spec.js',
  timeout: 5000,
  recursive: true,
  ui: 'bdd',
  reporter: 'spec'
};

Configuration de Babel

Creez ou modifiez le fichier babel.config.js :

// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', {
      targets: {
        node: 'current'
      }
    }]
  ]
};

Scripts npm

Ajoutez les scripts de test dans votre package.json :

{
  "scripts": {
    "test": "mocha",
    "test:watch": "mocha --watch",
    "test:coverage": "nyc mocha"
  }
}

Vue Test Utils : L’outil essentiel

Presentation de @vue/test-utils

@vue/test-utils est la bibliotheque officielle de Vue.js pour les tests unitaires. Elle fournit des utilitaires pour monter des composants, interagir avec eux et verifier leur comportement.

Methodes principales

Voici les methodes les plus utilisees :

import { mount, shallowMount } from '@vue/test-utils';
import MonComposant from '@/components/MonComposant.vue';

// Monter un composant
const wrapper = mount(MonComposant);

// Trouver un element
wrapper.find('.ma-classe');
wrapper.find('#mon-id');
wrapper.find('button');

// Trouver tous les elements correspondants
wrapper.findAll('.item');

// Verifier l'existence
wrapper.exists();

// Obtenir le texte
wrapper.text();

// Obtenir le HTML
wrapper.html();

// Acceder aux proprietes
wrapper.props();
wrapper.props('maProp');

// Acceder aux donnees
wrapper.vm.maVariable;

// Definir des donnees
await wrapper.setData({ maVariable: 'nouvelle valeur' });

// Definir des props
await wrapper.setProps({ maProp: 'nouvelle valeur' });

// Declencher un evenement
await wrapper.trigger('click');
await wrapper.trigger('submit');

// Definir la valeur d'un input
await wrapper.setValue('texte');

// Demonter le composant
wrapper.unmount();

mount vs shallowMount

Comprendre la difference

La distinction entre mount et shallowMount est cruciale pour ecrire des tests efficaces :

mount : Rend le composant avec tous ses composants enfants. Utile pour les tests d’integration.

shallowMount : Rend uniquement le composant cible, en stubant les composants enfants. Ideal pour les tests unitaires purs.

Exemple comparatif

// ComposantParent.vue
<template>
  <div class="parent">
    <h1>Parent</h1>
    <ComposantEnfant message="Bonjour" />
  </div>
</template>

<script>
import ComposantEnfant from './ComposantEnfant.vue';

export default {
  components: { ComposantEnfant }
};
</script>
// Tests comparatifs
import { mount, shallowMount } from '@vue/test-utils';
import ComposantParent from '@/components/ComposantParent.vue';

describe('mount vs shallowMount', () => {
  it('mount rend les composants enfants', () => {
    const wrapper = mount(ComposantParent);
    // Le contenu de ComposantEnfant est visible
    console.log(wrapper.html());
    // <div class="parent">
    //   <h1>Parent</h1>
    //   <div class="enfant">Bonjour</div>
    // </div>
  });

  it('shallowMount stube les composants enfants', () => {
    const wrapper = shallowMount(ComposantParent);
    // ComposantEnfant est remplace par un stub
    console.log(wrapper.html());
    // <div class="parent">
    //   <h1>Parent</h1>
    //   <composant-enfant-stub message="Bonjour"></composant-enfant-stub>
    // </div>
  });
});

Quand utiliser lequel ?

SituationRecommandation
Test unitaire purshallowMount
Test d’integrationmount
Composant avec beaucoup d’enfantsshallowMount
Verification des interactions parent-enfantmount
Tests de performanceshallowMount
Tests de formulaires completsmount

Simulation d’evenements DOM

Evenements de base

La simulation d’evenements est essentielle pour tester les interactions utilisateur :

import { mount } from '@vue/test-utils';
import Bouton from '@/components/Bouton.vue';

describe('Simulation d\'evenements', () => {
  it('gere le clic', async () => {
    const wrapper = mount(Bouton);

    await wrapper.find('button').trigger('click');

    expect(wrapper.emitted('clicked')).toBeTruthy();
  });

  it('gere le double-clic', async () => {
    const wrapper = mount(Bouton);

    await wrapper.find('button').trigger('dblclick');

    expect(wrapper.emitted('double-clicked')).toBeTruthy();
  });

  it('gere les touches clavier', async () => {
    const wrapper = mount(Bouton);

    await wrapper.find('input').trigger('keydown.enter');
    await wrapper.find('input').trigger('keydown.esc');
    await wrapper.find('input').trigger('keydown', { key: 'a' });
  });
});

Evenements de formulaire

import { mount } from '@vue/test-utils';
import Formulaire from '@/components/Formulaire.vue';

describe('Evenements de formulaire', () => {
  it('gere la soumission', async () => {
    const wrapper = mount(Formulaire);

    // Remplir les champs
    await wrapper.find('input[name="email"]').setValue('test@example.com');
    await wrapper.find('input[name="password"]').setValue('motdepasse123');

    // Soumettre le formulaire
    await wrapper.find('form').trigger('submit.prevent');

    // Verifier l'emission
    expect(wrapper.emitted('submit')).toBeTruthy();
    expect(wrapper.emitted('submit')[0][0]).toEqual({
      email: 'test@example.com',
      password: 'motdepasse123'
    });
  });

  it('gere le changement de valeur', async () => {
    const wrapper = mount(Formulaire);

    await wrapper.find('input').setValue('nouvelle valeur');
    await wrapper.find('input').trigger('change');

    expect(wrapper.vm.valeur).toBe('nouvelle valeur');
  });

  it('gere le focus et blur', async () => {
    const wrapper = mount(Formulaire);

    await wrapper.find('input').trigger('focus');
    expect(wrapper.vm.enFocus).toBe(true);

    await wrapper.find('input').trigger('blur');
    expect(wrapper.vm.enFocus).toBe(false);
  });
});

Evenements avec modificateurs

describe('Modificateurs d\'evenements', () => {
  it('gere les modificateurs de clic', async () => {
    const wrapper = mount(MonComposant);

    // Clic avec Ctrl
    await wrapper.trigger('click', { ctrlKey: true });

    // Clic avec Shift
    await wrapper.trigger('click', { shiftKey: true });

    // Clic droit
    await wrapper.trigger('contextmenu');
  });

  it('gere le scroll', async () => {
    const wrapper = mount(MonComposant);

    await wrapper.find('.container').trigger('scroll');
  });

  it('gere le survol', async () => {
    const wrapper = mount(MonComposant);

    await wrapper.find('.element').trigger('mouseenter');
    await wrapper.find('.element').trigger('mouseleave');
  });
});

Les Assertions Chai

Style expect (recommande)

Le style expect est le plus populaire et le plus lisible :

import { expect } from 'chai';

describe('Assertions avec expect', () => {
  it('verifie l\'egalite', () => {
    expect(2 + 2).to.equal(4);
    expect('hello').to.equal('hello');
    expect({ a: 1 }).to.deep.equal({ a: 1 });
  });

  it('verifie les types', () => {
    expect('string').to.be.a('string');
    expect(42).to.be.a('number');
    expect([]).to.be.an('array');
    expect({}).to.be.an('object');
    expect(null).to.be.null;
    expect(undefined).to.be.undefined;
  });

  it('verifie les booleens', () => {
    expect(true).to.be.true;
    expect(false).to.be.false;
    expect(1).to.be.ok; // truthy
    expect(0).to.not.be.ok; // falsy
  });

  it('verifie les chaines', () => {
    expect('hello world').to.include('world');
    expect('hello').to.have.lengthOf(5);
    expect('hello').to.match(/^hel/);
  });

  it('verifie les tableaux', () => {
    expect([1, 2, 3]).to.include(2);
    expect([1, 2, 3]).to.have.lengthOf(3);
    expect([1, 2, 3]).to.be.an('array').that.is.not.empty;
    expect([1, 2, 3]).to.have.members([3, 2, 1]);
  });

  it('verifie les objets', () => {
    expect({ a: 1 }).to.have.property('a');
    expect({ a: 1, b: 2 }).to.have.keys(['a', 'b']);
    expect({ a: 1 }).to.include({ a: 1 });
  });

  it('verifie les exceptions', () => {
    expect(() => { throw new Error('erreur'); }).to.throw();
    expect(() => { throw new Error('erreur'); }).to.throw('erreur');
    expect(() => { throw new Error('erreur'); }).to.throw(Error);
  });
});

Style should

Le style should etend les prototypes des objets :

import chai from 'chai';
chai.should();

describe('Assertions avec should', () => {
  it('utilise should', () => {
    const nombre = 5;
    nombre.should.equal(5);
    nombre.should.be.a('number');
    nombre.should.be.above(4);
    nombre.should.be.below(6);
  });

  it('chaine les assertions', () => {
    const texte = 'hello';
    texte.should.be.a('string').with.lengthOf(5);
  });
});

Style assert

Le style assert ressemble aux assertions classiques :

import { assert } from 'chai';

describe('Assertions avec assert', () => {
  it('utilise assert', () => {
    assert.equal(2 + 2, 4);
    assert.strictEqual(2 + 2, 4);
    assert.deepEqual({ a: 1 }, { a: 1 });
    assert.isTrue(true);
    assert.isFalse(false);
    assert.isArray([]);
    assert.isObject({});
    assert.isNull(null);
    assert.isUndefined(undefined);
    assert.lengthOf([1, 2, 3], 3);
    assert.include('hello', 'ell');
  });
});

Hooks de Test

Les quatre hooks principaux

Mocha fournit des hooks pour configurer et nettoyer l’environnement de test :

import { mount } from '@vue/test-utils';
import MonComposant from '@/components/MonComposant.vue';

describe('Hooks de test', () => {
  let wrapper;

  // Execute une fois avant tous les tests du bloc
  before(() => {
    console.log('Avant tous les tests');
    // Initialisation globale (connexion DB, setup global)
  });

  // Execute une fois apres tous les tests du bloc
  after(() => {
    console.log('Apres tous les tests');
    // Nettoyage global (deconnexion DB, cleanup)
  });

  // Execute avant chaque test
  beforeEach(() => {
    console.log('Avant chaque test');
    wrapper = mount(MonComposant);
  });

  // Execute apres chaque test
  afterEach(() => {
    console.log('Apres chaque test');
    wrapper.unmount();
  });

  it('premier test', () => {
    expect(wrapper.exists()).to.be.true;
  });

  it('deuxieme test', () => {
    expect(wrapper.exists()).to.be.true;
  });
});

Hooks imbriques

Les hooks peuvent etre imbriques dans des blocs describe :

describe('Composant Principal', () => {
  let wrapper;

  beforeEach(() => {
    wrapper = mount(ComposantPrincipal);
  });

  afterEach(() => {
    wrapper.unmount();
  });

  describe('Etat initial', () => {
    it('affiche le titre', () => {
      expect(wrapper.find('h1').exists()).to.be.true;
    });
  });

  describe('Apres interaction', () => {
    beforeEach(async () => {
      // Configuration specifique pour ce groupe
      await wrapper.find('button').trigger('click');
    });

    it('affiche le contenu', () => {
      expect(wrapper.find('.contenu').exists()).to.be.true;
    });

    it('cache le bouton', () => {
      expect(wrapper.find('button').exists()).to.be.false;
    });
  });
});

Tests Asynchrones

Utilisation de async/await

La gestion de l’asynchronicite est essentielle pour tester Vue.js :

import { mount, flushPromises } from '@vue/test-utils';
import ComposantAsync from '@/components/ComposantAsync.vue';

describe('Tests asynchrones', () => {
  it('attend les promesses avec async/await', async () => {
    const wrapper = mount(ComposantAsync);

    // Declencher une action asynchrone
    await wrapper.find('button').trigger('click');

    // Attendre que Vue mette a jour le DOM
    await wrapper.vm.$nextTick();

    expect(wrapper.find('.resultat').text()).to.equal('Charge');
  });

  it('utilise flushPromises', async () => {
    const wrapper = mount(ComposantAsync);

    await wrapper.find('button').trigger('click');
    await flushPromises();

    expect(wrapper.find('.resultat').text()).to.equal('Donnees chargees');
  });

  it('teste les appels API', async () => {
    // Mock de l'API
    const mockApi = {
      getData: () => Promise.resolve({ data: 'test' })
    };

    const wrapper = mount(ComposantAsync, {
      global: {
        provide: {
          api: mockApi
        }
      }
    });

    await wrapper.find('.charger').trigger('click');
    await flushPromises();

    expect(wrapper.vm.donnees).to.equal('test');
  });
});

Gestion des timeouts

describe('Tests avec delais', () => {
  it('gere les delais avec setTimeout', async () => {
    const wrapper = mount(ComposantAvecDelai);

    // Declencher l'action
    await wrapper.find('button').trigger('click');

    // Attendre le delai
    await new Promise(resolve => setTimeout(resolve, 100));
    await wrapper.vm.$nextTick();

    expect(wrapper.vm.termine).to.be.true;
  });

  it('utilise les timers simules', async () => {
    const clock = sinon.useFakeTimers();

    const wrapper = mount(ComposantAvecDelai);
    await wrapper.find('button').trigger('click');

    clock.tick(100);
    await wrapper.vm.$nextTick();

    expect(wrapper.vm.termine).to.be.true;

    clock.restore();
  });
});

Preparation du Projet

Pour commencer, nous devons creer un nouveau projet Vue.js avec le CLI (Command Line Interface) et selectionner la solution d’unit testing Mocha + Chai. Si vous avez deja cree votre projet, assurez-vous que vous avez installe les dependances necessaires en executant npm install dans votre terminal.

Structure du Projet

Lorsque nous avons cree notre projet, nous devrions avoir une structure de repertoire similaire a celle-ci :

testing/
  basics/
  src/
    App.vue
    main.js
    components/
      Formulaire.vue
      Liste.vue
      Bouton.vue
  tests/
    unit/
      App.spec.js
      Formulaire.spec.js
      Liste.spec.js
  package.json
  .mocharc.js
  babel.config.js

Nous allons nous concentrer sur le dossier src/ et le fichier App.vue, qui est notre composant principal a tester.


Composant App.vue

Le fichier App.vue contient un simple formulaire qui permet d’ajouter des elements a une liste. Voici son contenu :

<template>
  <div id="app" class="ui text container">
    <table class="ui selectable structured large table">
      <thead>
        <tr>
          <th>Items</th>
        </tr>
      </thead>
      <tbody class="item-list">
        <tr v-for="(item, index) in items" :key="index">
          <td>{{ item }}</td>
        </tr>
      </tbody>
      <tfoot>
        <tr>
          <th>
            <form class="ui form" @submit="addItem">
              <div class="field">
                <input v-model="item" type="text" class="prompt" placeholder="Add item...">
              </div>
              <button type="submit" class="ui button" :disabled="!item">Ajouter</button>
              <span @click="removeAllItems" class="ui label">Supprimer tout</span>
            </form>
          </th>
        </tr>
      </tfoot>
    </table>
  </div>
</template>

<script>
export default {
  name: 'app',
  data() {
    return {
      item: '',
      items: []
    };
  },
  methods: {
    addItem(evt) {
      evt.preventDefault();
      this.items.push(this.item);
      this.item = '';
    },
    removeAllItems() {
      this.items = [];
    }
  }
};
</script>

<style>
/* styles */
</style>

Ecriture des Tests Complets

Maintenant que nous avons notre composant, nous allons ecrire les tests complets pour le fichier App.spec.js. Nous utiliserons Mocha et Chai pour ecrire nos tests.

// tests/unit/App.spec.js

import { expect } from 'chai';
import { mount, shallowMount } from '@vue/test-utils';
import App from '@/App.vue';

describe('App.vue', () => {
  let wrapper;

  beforeEach(() => {
    wrapper = mount(App);
  });

  afterEach(() => {
    wrapper.unmount();
  });

  describe('Rendu initial', () => {
    it('affiche le conteneur principal', () => {
      expect(wrapper.find('#app').exists()).to.be.true;
    });

    it('affiche un formulaire vide', () => {
      expect(wrapper.find('.ui.form').exists()).to.be.true;
      expect(wrapper.find('.prompt').element.value).to.equal('');
    });

    it('affiche une liste vide au demarrage', () => {
      expect(wrapper.findAll('.item-list tr')).to.have.lengthOf(0);
    });

    it('a le bouton desactive initialement', () => {
      expect(wrapper.find('button[type="submit"]').attributes('disabled')).to.exist;
    });
  });

  describe('Ajout d\'elements', () => {
    it('active le bouton quand un texte est saisi', async () => {
      await wrapper.find('.prompt').setValue('Test');
      expect(wrapper.find('button[type="submit"]').attributes('disabled')).to.be.undefined;
    });

    it('ajoute un element a la liste', async () => {
      const item = 'Nouvel element';

      await wrapper.find('.prompt').setValue(item);
      await wrapper.find('form').trigger('submit');

      expect(wrapper.find('.item-list').text()).to.include(item);
      expect(wrapper.findAll('.item-list tr')).to.have.lengthOf(1);
    });

    it('vide le champ apres ajout', async () => {
      await wrapper.find('.prompt').setValue('Test');
      await wrapper.find('form').trigger('submit');

      expect(wrapper.find('.prompt').element.value).to.equal('');
    });

    it('ajoute plusieurs elements', async () => {
      const elements = ['Element 1', 'Element 2', 'Element 3'];

      for (const element of elements) {
        await wrapper.find('.prompt').setValue(element);
        await wrapper.find('form').trigger('submit');
      }

      expect(wrapper.findAll('.item-list tr')).to.have.lengthOf(3);
      elements.forEach(element => {
        expect(wrapper.find('.item-list').text()).to.include(element);
      });
    });
  });

  describe('Suppression d\'elements', () => {
    beforeEach(async () => {
      // Ajouter des elements avant chaque test de suppression
      await wrapper.find('.prompt').setValue('Element 1');
      await wrapper.find('form').trigger('submit');
      await wrapper.find('.prompt').setValue('Element 2');
      await wrapper.find('form').trigger('submit');
    });

    it('supprime tous les elements', async () => {
      expect(wrapper.findAll('.item-list tr')).to.have.lengthOf(2);

      await wrapper.find('.ui.label').trigger('click');

      expect(wrapper.findAll('.item-list tr')).to.have.lengthOf(0);
    });

    it('permet d\'ajouter apres suppression', async () => {
      await wrapper.find('.ui.label').trigger('click');
      await wrapper.find('.prompt').setValue('Nouvel element');
      await wrapper.find('form').trigger('submit');

      expect(wrapper.findAll('.item-list tr')).to.have.lengthOf(1);
    });
  });

  describe('Donnees reactives', () => {
    it('met a jour v-model correctement', async () => {
      await wrapper.find('.prompt').setValue('Test');
      expect(wrapper.vm.item).to.equal('Test');
    });

    it('synchronise le tableau items', async () => {
      await wrapper.find('.prompt').setValue('Test');
      await wrapper.find('form').trigger('submit');

      expect(wrapper.vm.items).to.deep.equal(['Test']);
    });
  });
});

Exemples de Tests pour Differents Composants

Test d’un composant de formulaire avance

// tests/unit/FormulaireContact.spec.js
import { expect } from 'chai';
import { mount } from '@vue/test-utils';
import FormulaireContact from '@/components/FormulaireContact.vue';

describe('FormulaireContact.vue', () => {
  let wrapper;

  beforeEach(() => {
    wrapper = mount(FormulaireContact);
  });

  describe('Validation', () => {
    it('affiche une erreur pour email invalide', async () => {
      await wrapper.find('input[type="email"]').setValue('email-invalide');
      await wrapper.find('form').trigger('submit');

      expect(wrapper.find('.erreur-email').text()).to.include('Email invalide');
    });

    it('valide un email correct', async () => {
      await wrapper.find('input[type="email"]').setValue('test@example.com');
      await wrapper.find('form').trigger('submit');

      expect(wrapper.find('.erreur-email').exists()).to.be.false;
    });

    it('requiert tous les champs obligatoires', async () => {
      await wrapper.find('form').trigger('submit');

      expect(wrapper.findAll('.champ-requis')).to.have.lengthOf.above(0);
    });
  });

  describe('Soumission', () => {
    it('emet les donnees du formulaire', async () => {
      await wrapper.find('input[name="nom"]').setValue('Jean Dupont');
      await wrapper.find('input[type="email"]').setValue('jean@example.com');
      await wrapper.find('textarea').setValue('Mon message');
      await wrapper.find('form').trigger('submit');

      expect(wrapper.emitted('envoyer')).to.have.lengthOf(1);
      expect(wrapper.emitted('envoyer')[0][0]).to.deep.equal({
        nom: 'Jean Dupont',
        email: 'jean@example.com',
        message: 'Mon message'
      });
    });
  });
});

Test d’un composant avec props

// tests/unit/CarteUtilisateur.spec.js
import { expect } from 'chai';
import { mount } from '@vue/test-utils';
import CarteUtilisateur from '@/components/CarteUtilisateur.vue';

describe('CarteUtilisateur.vue', () => {
  const utilisateurDefaut = {
    nom: 'Marie Martin',
    email: 'marie@example.com',
    avatar: '/images/avatar.jpg',
    role: 'Admin'
  };

  it('affiche les informations utilisateur', () => {
    const wrapper = mount(CarteUtilisateur, {
      props: {
        utilisateur: utilisateurDefaut
      }
    });

    expect(wrapper.find('.nom').text()).to.equal('Marie Martin');
    expect(wrapper.find('.email').text()).to.equal('marie@example.com');
    expect(wrapper.find('.role').text()).to.equal('Admin');
  });

  it('affiche un avatar par defaut si non fourni', () => {
    const wrapper = mount(CarteUtilisateur, {
      props: {
        utilisateur: { ...utilisateurDefaut, avatar: null }
      }
    });

    expect(wrapper.find('img').attributes('src')).to.equal('/images/default-avatar.png');
  });

  it('emet un evenement au clic sur modifier', async () => {
    const wrapper = mount(CarteUtilisateur, {
      props: {
        utilisateur: utilisateurDefaut,
        editable: true
      }
    });

    await wrapper.find('.btn-modifier').trigger('click');

    expect(wrapper.emitted('modifier')).to.have.lengthOf(1);
    expect(wrapper.emitted('modifier')[0][0]).to.deep.equal(utilisateurDefaut);
  });
});

Test d’un composant avec slots

// tests/unit/Modal.spec.js
import { expect } from 'chai';
import { mount } from '@vue/test-utils';
import Modal from '@/components/Modal.vue';

describe('Modal.vue', () => {
  it('affiche le contenu du slot par defaut', () => {
    const wrapper = mount(Modal, {
      slots: {
        default: '<p>Contenu de la modal</p>'
      }
    });

    expect(wrapper.find('.modal-body').html()).to.include('Contenu de la modal');
  });

  it('affiche le titre via slot nomme', () => {
    const wrapper = mount(Modal, {
      slots: {
        header: '<h2>Mon Titre</h2>',
        default: '<p>Contenu</p>',
        footer: '<button>Fermer</button>'
      }
    });

    expect(wrapper.find('.modal-header').html()).to.include('Mon Titre');
    expect(wrapper.find('.modal-footer').html()).to.include('Fermer');
  });

  it('ferme la modal au clic exterieur', async () => {
    const wrapper = mount(Modal, {
      props: { visible: true }
    });

    await wrapper.find('.modal-overlay').trigger('click');

    expect(wrapper.emitted('fermer')).to.have.lengthOf(1);
  });
});

Comparatif : Mocha vs Jest vs Vitest

CaracteristiqueMocha + ChaiJestVitest
VitesseMoyenneMoyenneTres rapide
ConfigurationManuelleZero-configZero-config
AssertionsVia ChaiIntegreesIntegrees
MockingVia SinonIntegreIntegre
CouvertureVia Istanbul/NYCIntegreeIntegree
Watch modeOuiOuiOui (HMR)
ParallelisationLimiteeOuiOui
Support TypeScriptVia pluginsNatifNatif
SnapshotsNonOuiOui
EcosystemeMatureTres largeEn croissance
Vue.js supportBonBonExcellent
Courbe d’apprentissageModereeFacileFacile

Quand choisir Mocha + Chai ?

  • Projets existants utilisant deja Mocha
  • Besoin de flexibilite maximale dans la configuration
  • Equipe familiere avec l’ecosysteme Mocha
  • Integration avec des outils specifiques

Quand choisir Jest ?

  • Nouveaux projets React ou Vue
  • Preference pour une solution tout-en-un
  • Besoin de snapshots testing
  • Large communaute et documentation

Quand choisir Vitest ?

  • Projets utilisant Vite comme bundler
  • Performance critique (grands projets)
  • Projets Vue 3 modernes
  • Migration depuis Jest (API compatible)

Bonnes Pratiques

1. Isoler chaque test

Chaque test doit etre independant et ne pas dependre de l’ordre d’execution :

// BON : Chaque test cree son propre contexte
describe('MonComposant', () => {
  let wrapper;

  beforeEach(() => {
    wrapper = mount(MonComposant);
  });

  afterEach(() => {
    wrapper.unmount();
  });
});

2. Utiliser des noms descriptifs

Les noms de tests doivent decrire le comportement attendu :

// MAUVAIS
it('test1', () => { ... });

// BON
it('affiche un message d\'erreur quand le formulaire est invalide', () => { ... });

3. Tester le comportement, pas l’implementation

// MAUVAIS : Teste l'implementation interne
it('appelle la methode interne', () => {
  expect(wrapper.vm.methodeInterne).to.have.been.called;
});

// BON : Teste le resultat visible
it('affiche le resultat apres traitement', async () => {
  await wrapper.find('button').trigger('click');
  expect(wrapper.find('.resultat').text()).to.equal('Succes');
});

4. Eviter les tests fragiles

// MAUVAIS : Depend de la structure exacte du DOM
expect(wrapper.find('div > ul > li:first-child > span').text()).to.equal('Test');

// BON : Utilise des selecteurs semantiques
expect(wrapper.find('[data-testid="premier-element"]').text()).to.equal('Test');

5. Grouper les tests logiquement

describe('Formulaire', () => {
  describe('Validation', () => {
    it('valide l\'email', () => { ... });
    it('valide le mot de passe', () => { ... });
  });

  describe('Soumission', () => {
    it('soumet avec des donnees valides', () => { ... });
    it('affiche une erreur en cas d\'echec', () => { ... });
  });
});

6. Utiliser des fixtures reutilisables

// fixtures/utilisateurs.js
export const utilisateurAdmin = {
  id: 1,
  nom: 'Admin',
  role: 'admin'
};

export const utilisateurStandard = {
  id: 2,
  nom: 'User',
  role: 'user'
};

// Dans les tests
import { utilisateurAdmin, utilisateurStandard } from '../fixtures/utilisateurs';

Pieges Courants

1. Oublier d’attendre les mises a jour asynchrones

// MAUVAIS : Le DOM n'est pas encore mis a jour
wrapper.find('button').trigger('click');
expect(wrapper.find('.resultat').exists()).to.be.true; // Peut echouer

// BON : Attendre la mise a jour
await wrapper.find('button').trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.find('.resultat').exists()).to.be.true;

2. Ne pas nettoyer apres les tests

// MAUVAIS : Fuite de memoire et effets de bord
it('mon test', () => {
  const wrapper = mount(MonComposant);
  // ... test sans unmount
});

// BON : Toujours nettoyer
afterEach(() => {
  wrapper.unmount();
});

3. Tester trop de choses dans un seul test

// MAUVAIS : Un seul test fait tout
it('teste le formulaire', async () => {
  // Verifie le rendu initial
  // Verifie la validation
  // Verifie la soumission
  // Verifie le message de succes
  // ...
});

// BON : Un test = une assertion logique
it('affiche le formulaire vide au chargement', () => { ... });
it('affiche une erreur pour email invalide', () => { ... });
it('soumet les donnees correctement', () => { ... });

4. Confondre mount et shallowMount

// PROBLEME : mount peut etre lent avec beaucoup d'enfants
const wrapper = mount(ComposantAvecBeaucoupDEnfants);

// SOLUTION : Utiliser shallowMount pour les tests unitaires
const wrapper = shallowMount(ComposantAvecBeaucoupDEnfants);

5. Ignorer les cas limites

// N'oubliez pas de tester :
// - Les tableaux vides
// - Les valeurs null/undefined
// - Les chaines vides
// - Les valeurs extremes
// - Les erreurs reseau
// - Les timeouts

Execution des Tests

Pour executer les tests, nous pouvons utiliser differentes commandes dans notre terminal.

Commandes de base

# Executer tous les tests
npm run test

# Executer en mode watch
npm run test:watch

# Executer avec couverture de code
npm run test:coverage

# Executer un fichier specifique
npx mocha tests/unit/App.spec.js

# Executer les tests correspondant a un pattern
npx mocha --grep "ajoute un element"

Interpretation des resultats

  App.vue
    Rendu initial
 affiche le conteneur principal
 affiche un formulaire vide
 affiche une liste vide au demarrage
 a le bouton desactive initialement
    Ajout d'elements
      ✓ active le bouton quand un texte est saisi
      ✓ ajoute un element a la liste
      ✓ vide le champ apres ajout
      ✓ ajoute plusieurs elements
    Suppression d'elements
 supprime tous les elements
 permet d'ajouter apres suppression

  10 passing (234ms)

Si tout se passe bien, vous devriez voir un message indiquant que les tests ont reussi avec des coches vertes.


Conclusion

Les tests unitaires avec Mocha et Chai offrent une solution robuste et flexible pour garantir la qualite de vos composants Vue.js. Ce guide vous a presente les concepts fondamentaux et les techniques avancees pour maitriser le testing dans l’ecosysteme Vue.

Points cles a retenir

  1. Configuration solide : Prenez le temps de bien configurer votre environnement de test
  2. mount vs shallowMount : Choisissez judicieusement selon le type de test
  3. Assertions claires : Utilisez le style Chai qui vous convient le mieux
  4. Tests asynchrones : N’oubliez jamais d’attendre les promesses et les mises a jour du DOM
  5. Bonnes pratiques : Suivez les conventions pour des tests maintenables

Pour aller plus loin

  • Explorez les mocks et stubs avec Sinon.js pour isoler les dependances
  • Implementez des tests d’integration pour verifier les interactions entre composants
  • Configurez la couverture de code avec Istanbul/NYC
  • Integrez les tests dans votre pipeline CI/CD
  • Considerez Vitest pour les nouveaux projets Vue 3 avec Vite

L’investissement dans les tests unitaires paie toujours sur le long terme. Des tests bien ecrits vous permettront de refactorer en confiance, d’accelerer le developpement et de livrer des applications plus fiables.

N’hesitez pas a partager vos questions ou experiences dans les commentaires. Bonne continuation dans votre apprentissage du testing Vue.js !

Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles