Aller au contenu principal
Version: 26.x

Simulations manuelles

Les simulations manuelles sont utilisées pour remplacer une fonctionnalité par des données simulées. Par exemple, au lieu d'accéder à une ressource distante comme un site web ou une base de données, vous pouvez créer une simulation manuelle qui vous permet d'utiliser des données factices. Cela permet de s'assurer que vos tests seront rapides et non bancals.

Simulation des modules utilisateurs#

Les simulations manuelles sont définies en écrivant un module dans un sous-répertoire __mocks__/ immédiatement adjacent au module. Par exemple, pour simuler un module appelé user dans le répertoire models, créez un fichier appelé user.js et placez-le dans le répertoire models/__mocks__. Remarquez que le dossier __mocks__ est sensible à la casse, donc nommer le répertoire __MOCKS__ sera incompatible avec certains systèmes.

Lorsque nous avons besoin de ce module dans nos tests (ce qui signifie que nous voulons utiliser la simulation manuelle au lieu de l'implémentation réelle), l'appel explicite de jest.mock('./nomModule') est exigé.

Simulation des modules Node#

Si le module que vous simulez est un module Node (par exemple : lodash), le simulateur doit être placé dans le répertoire __mocks__ adjacent à node_modules (sauf si vous avez configuré [roots](Configuration. md#roots-arraystring) pour pointer vers un dossier autre que la racine du projet) et sera automatiquement simulé. Il n'est pas nécessaire d'appeler explicitement jest.mock('nom_module').

Les modules étendus (également appelés paquets étendus) peuvent être simulés en créant un fichier dans une structure de répertoire qui correspond au nom du module étendu. Par exemple, pour simuler un module étendu appelé @scope/nom-du-projet, créez un fichier à __mocks__/@scope/nom-du-projet.js, en créant le répertoire @scope/ en conséquence.

Avertissement : Si nous voulons simuler les modules de base de Node (par exemple : fs ou path), alors l'appel explicite par exemple jest.mock('path') est exigé, car les modules de base de Node ne sont pas simulés par défaut.

Exemples#

.
├── config
├── __mocks__
│   └── fs.js
├── models
│   ├── __mocks__
│   │   └── user.js
│   └── user.js
├── node_modules
└── views

Lorsqu'une simulation manuelle existe pour un module donné, le système de modules de Jest utilisera ce module lors de l'appel explicite de jest.mock('nomModule'). Cependant, lorsque automock est défini à true, l'implémentation de la simulation manuelle sera utilisée au lieu de la simulation créée automatiquement, même si jest.mock('nomModule') n'est pas appelé. Pour ne pas adopter ce comportement, vous devrez appeler explicitement jest.unmock('nomModule') dans les tests qui doivent utiliser l'implémentation réelle du module.

Remarque : pour que la simulation soit correcte, Jest a besoin que jest.mock('nomModule') soit dans la même portée que l'instruction require/import.

Voici un exemple fictif où nous avons un module qui fournit un récapitulatif de tous les fichiers d'un répertoire donné. Dans ce cas, nous utilisons le module fs de base (intégré).

// FileSummarizer.js
'use strict';
const fs = require('fs');
function summarizeFilesInDirectorySync(directory) {
return fs.readdirSync(directory).map(fileName => ({
directory,
fileName,
}));
}
exports.summarizeFilesInDirectorySync = summarizeFilesInDirectorySync;

Puisque nous souhaitons que nos tests évitent de toucher le disque (ce qui est plutôt lent et fragile), nous créons un objet de simulation manuelle pour le module fs en étendant un objet de simulation automatique. Notre simulation manuelle implémentera des versions personnalisées des API fs sur lesquelles nous pourrons nous appuyer pour nos tests :

// __mocks__/fs.js
'use strict';
const path = require('path');
const fs = jest.createMockFromModule('fs');
// Il s'agit d'une fonction personnalisée que nos tests peuvent utiliser pendant la préparation pour spécifier
// à quoi doivent ressembler les fichiers sur le système de fichiers "fictif" lorsque
// l'une des APIs `fs` est utilisée.
let mockFiles = Object.create(null);
function __setMockFiles(newMockFiles) {
mockFiles = Object.create(null);
for (const file in newMockFiles) {
const dir = path.dirname(file);
if (!mockFiles[dir]) {
mockFiles[dir] = [];
}
mockFiles[dir].push(path.basename(file));
}
}
// Une version personnalisée de `readdirSync' qui lit à partir
// de la liste de fichiers simulés définie via __setMockFiles
function readdirSync(directoryPath) {
return mockFiles[directoryPath] || [];
}
fs.__setMockFiles = __setMockFiles;
fs.readdirSync = readdirSync;
module.exports = fs;

Maintenant, nous écrivons notre test. Notez que nous devons dire explicitement que nous voulons simuler le module fs parce que c'est un module de base de Node :

// __tests__/FileSummarizer-test.js
'use strict';
jest.mock('fs');
describe('listFilesInDirectorySync', () => {
const MOCK_FILE_INFO = {
'/path/to/file1.js': 'console.log("contenu de file1");',
'/path/to/file2.txt': 'contenu de file2',
};
beforeEach(() => {
// Préparation d'informations simulées sur les fichiers avant chaque test.
require('fs').__setMockFiles(MOCK_FILE_INFO);
});
test('inclut tous les fichiers du répertoire dans le récapitulatif', () => {
const FileSummarizer = require('../FileSummarizer');
const fileSummary =
FileSummarizer.summarizeFilesInDirectorySync('/path/to');
expect(fileSummary.length).toBe(2);
});
});

L'exemple de simulation présenté ici utilise jest.createMockFromModule pour générer une simulation automatique et remplace son comportement par défaut. Il s'agit de l'approche recommandée, mais elle est totalement facultative. Si vous ne souhaitez pas du tout utiliser la simulation automatique, vous pouvez exporter vos propres fonctions à partir du fichier mock. L'inconvénient des simulations entièrement manuelles est qu'elles sont manuelles - ce qui signifie que vous devez les mettre à jour manuellement chaque fois que le module qu'elles simulent change. Pour cette raison, il est préférable d'utiliser ou d'étendre la simulation automatique lorsque cela répond à vos besoins.

Pour s'assurer qu'une simulation manuelle et son implémentation réelle restent synchronisées, il peut être utile de faire un « require » du module réel en utilisant jest.requireActual(moduleName) dans votre simulation manuelle et en la modifiant avec des fonctions de simulation avant de l'exporter.

Le code pour cet exemple est disponible dans examples/manual-mocks.

Utilisation avec les importations de modules ES#

Si vous utilisez des importations de modules ES, vous serez normalement tenté de placer vos déclarations import en haut du fichier de test. Mais il est souvent nécessaire d'indiquer à Jest d'utiliser une simulation avant que les modules ne l'utilisent. Pour cette raison, Jest va automatiquement faire remonter les appels jest.mock au début du module (avant toute importation). Pour en savoir plus à ce sujet et le voir en action, consultez ce dépôt.

Simulation de méthodes qui ne sont pas implémentées dans JSDOM#

Si un code utilise une méthode que JSDOM (l'implémentation DOM utilisée par Jest) n'a pas encore implémentée, il n'est pas toujours possible de la tester facilement. C'est par exemple le cas avec window.matchMedia(). Jest renvoie TypeError: window.matchMedia is not a function et n'exécute pas correctement le test.

Dans ce cas, la simulation de matchMedia dans le fichier de test devrait résoudre le problème :

Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // déprécié
removeListener: jest.fn(), // déprécié
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});

Cela fonctionne si window.matchMedia() est utilisé dans une fonction (ou méthode) qui est appelée dans le test. Si window.matchMedia() est exécuté directement dans le fichier testé, Jest signale la même erreur. Dans ce cas, la solution est de déplacer la simulation manuelle dans un fichier séparé et d'inclure celui-ci dans le test avant le fichier testé :

import './matchMedia.mock'; // Doit être importé avant le fichier testé
import {myMethod} from './file-to-test';
describe('myMethod()', () => {
// Teste la méthode ici...
});