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__
.
The __mocks__
folder is case-sensitive, so naming the directory __MOCKS__
will break on some systems.
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.
If we want to mock Node's built-in modules (e.g.: fs
or path
), then explicitly calling e.g. jest.mock('path')
is required, because built-in modules are not mocked by default.
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.
In order to mock properly, Jest needs jest.mock('moduleName')
to be in the same scope as the require/import
statement.
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é).
'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 :
'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. In this case jest.mock('fs')
must be called explicitly, because fs
is Node’s built-in module:
'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.
The code for this example is available at 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.
jest.mock
calls cannot be hoisted to the top of the module if you enabled ECMAScript modules support. The ESM module loader always evaluates the static imports before executing code. See ECMAScriptModules for details.
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...
});