Aller au contenu principal
Version : Suivant

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__.

attention

The __mocks__ folder is case-sensitive, so naming the directory __MOCKS__ will break on some systems.

remarque

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.

attention

If we want to mock Node's build-in modules (e.g.: fs or path), then explicitly calling e.g. jest.mock('path') is required, because build-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.

info

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é).

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. In this case jest.mock('fs') must be called explicitly, because fs is Node’s build-in module:

__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.

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.

attention

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...
});