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 ?
| Situation | Recommandation |
|---|---|
| Test unitaire pur | shallowMount |
| Test d’integration | mount |
| Composant avec beaucoup d’enfants | shallowMount |
| Verification des interactions parent-enfant | mount |
| Tests de performance | shallowMount |
| Tests de formulaires complets | mount |
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
| Caracteristique | Mocha + Chai | Jest | Vitest |
|---|---|---|---|
| Vitesse | Moyenne | Moyenne | Tres rapide |
| Configuration | Manuelle | Zero-config | Zero-config |
| Assertions | Via Chai | Integrees | Integrees |
| Mocking | Via Sinon | Integre | Integre |
| Couverture | Via Istanbul/NYC | Integree | Integree |
| Watch mode | Oui | Oui | Oui (HMR) |
| Parallelisation | Limitee | Oui | Oui |
| Support TypeScript | Via plugins | Natif | Natif |
| Snapshots | Non | Oui | Oui |
| Ecosysteme | Mature | Tres large | En croissance |
| Vue.js support | Bon | Bon | Excellent |
| Courbe d’apprentissage | Moderee | Facile | Facile |
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
- Configuration solide : Prenez le temps de bien configurer votre environnement de test
- mount vs shallowMount : Choisissez judicieusement selon le type de test
- Assertions claires : Utilisez le style Chai qui vous convient le mieux
- Tests asynchrones : N’oubliez jamais d’attendre les promesses et les mises a jour du DOM
- 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 !
In-Article Ad
Dev Mode
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
Tests unitaires Vue.js : Simuler les interactions utilisateur avec Jest
Apprenez a tester vos composants Vue.js avec Jest et Vue Test Utils. Guide pratique pour simuler les saisies et soumissions de formulaires.
Mocker les appels API avec Sinon et Chai dans vos tests Vue.js
Guide complet pour utiliser Sinon et Chai afin de mocker les actions Vuex et tester vos composants Vue.js avec des donnees simulees.
Vue.js et TypeScript : Guide Complet pour la Composition API avec Typage Statique
Combinez Vue 3, TypeScript et la Composition API pour un code plus sur. Configuration, typage et bonnes pratiques pour vos projets Vue.