Aller au contenu principal
Version : Suivant

Mocks manuels

Traduction Bêta Non Officielle

Cette page a été traduite par PageTurner AI (bêta). Non approuvée officiellement par le projet. Vous avez trouvé une erreur ? Signaler un problème →

Les mocks manuels permettent de remplacer des fonctionnalités par des données simulées. Par exemple, plutôt que d'accéder à une ressource distante comme un site web ou une base de données, vous pouvez créer un mock manuel utilisant de fausses données. Cela garantit que vos tests seront rapides et non flaky.

Simuler des modules utilisateur

Les mocks manuels se définissent en créant un module dans un sous-répertoire __mocks__/ adjacent au module cible. Par exemple, pour simuler un module user dans le répertoire models, créez un fichier user.js dans le dossier models/__mocks__.

attention

Le dossier __mocks__ est sensible à la casse : nommer le répertoire __MOCKS__ provoquera des erreurs sur certains systèmes.

note

Lorsqu'on requiert ce module dans nos tests (pour utiliser le mock manuel plutôt que l'implémentation réelle), l'appel explicite à jest.mock('./moduleName') est obligatoire.

Simuler des modules Node

Pour un module Node (ex: lodash), placez le mock dans __mocks__ adjacent à node_modules (sauf si roots pointe vers un autre dossier racine). Le mock sera appliqué automatiquement sans appel explicite à jest.mock('module_name').

Les scoped modules (ou scoped packages) se simulent en créant un fichier dans une arborescence correspondant à leur nom. Par exemple, pour le module @scope/project-name, créez __mocks__/@scope/project-name.js en générant le dossier @scope/.

attention

Pour les modules natifs de Node (ex: fs ou path), l'appel explicite comme jest.mock('path') est requis car ils ne sont pas mockés par défaut.

Exemples

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

Quand un mock manuel existe pour un module, Jest l'utilisera lors d'un appel explicite à jest.mock('moduleName'). Cependant, si automock vaut true, l'implémentation manuelle remplacera le mock automatique même sans appel à jest.mock('moduleName'). Pour désactiver ce comportement, appelez jest.unmock('moduleName') dans les tests nécessitant l'implémentation réelle.

info

Pour une simulation correcte, jest.mock('moduleName') doit être dans la même portée que l'instruction require/import.

Voici un exemple artificiel où un module fournit un résumé des fichiers d'un répertoire, en utilisant le module natif fs.

FileSummarizer.js
'use strict';

const fs = require('fs');

function summarizeFilesInDirectorySync(directory) {
return fs.readdirSync(directory).map(fileName => ({
directory,
fileName,
}));
}

exports.summarizeFilesInDirectorySync = summarizeFilesInDirectorySync;

Pour éviter les accès disque (lents et fragiles), nous créons un mock manuel de fs en étendant un mock automatique. Notre implémentation personnalisée fournira des versions adaptées des APIs fs pour nos tests :

__mocks__/fs.js
'use strict';

const path = require('path');

const fs = jest.createMockFromModule('fs');

// This is a custom function that our tests can use during setup to specify
// what the files on the "mock" filesystem should look like when any of the
// `fs` APIs are used.
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));
}
}

// A custom version of `readdirSync` that reads from the special mocked out
// file list set via __setMockFiles
function readdirSync(directoryPath) {
return mockFiles[directoryPath] || [];
}

fs.__setMockFiles = __setMockFiles;
fs.readdirSync = readdirSync;

module.exports = fs;

Écrivons maintenant notre test. Ici, jest.mock('fs') doit être appelé explicitement car fs est un module natif de Node :

__tests__/FileSummarizer-test.js
'use strict';

jest.mock('fs');

describe('listFilesInDirectorySync', () => {
const MOCK_FILE_INFO = {
'/path/to/file1.js': 'console.log("file1 contents");',
'/path/to/file2.txt': 'file2 contents',
};

beforeEach(() => {
// Set up some mocked out file info before each test
require('fs').__setMockFiles(MOCK_FILE_INFO);
});

test('includes all files in the directory in the summary', () => {
const FileSummarizer = require('../FileSummarizer');
const fileSummary =
FileSummarizer.summarizeFilesInDirectorySync('/path/to');

expect(fileSummary.length).toBe(2);
});
});

L'exemple utilise jest.createMockFromModule pour générer un mock automatique avant de le personnaliser. Cette approche est recommandée mais optionnelle. Si vous ne voulez pas utiliser le mock automatique, exportez vos propres fonctions depuis le fichier de mock. Un inconvénient des mocks entièrement manuels est leur maintenance : vous devez les mettre à jour manuellement quand le module mocké change. Privilégiez donc l'extension du mock automatique lorsque possible.

Pour garantir la synchronisation entre un mock manuel et son implémentation réelle, il peut être utile d'importer le module réel via jest.requireActual(moduleName) dans votre mock manuel, puis de le modifier avec des fonctions simulées avant de l'exporter.

Le code de cet exemple est disponible sur examples/manual-mocks.

Utilisation avec les imports de modules ES

Si vous utilisez des imports de modules ES, vous placerez normalement vos déclarations import en haut du fichier de test. Cependant, vous devez souvent indiquer à Jest d'utiliser un mock avant que les modules ne l'utilisent. Pour cette raison, Jest remontera automatiquement les appels jest.mock en haut du module (avant tout import). Pour en savoir plus et voir cela en action, consultez ce dépôt.

attention

Les appels jest.mock ne peuvent pas être remontés en haut du module si le support des modules ECMAScript est activé. Le chargeur de modules ESM évalue toujours les imports statiques avant d'exécuter le code. Voir ECMAScriptModules pour plus de détails.

Simulation de méthodes non implémentées dans JSDOM

Lorsqu'un code utilise une méthode que JSDOM (l'implémentation DOM utilisée par Jest) n'a pas encore implémentée, son test devient difficile. C'est par exemple le cas avec window.matchMedia(). Jest renvoie alors TypeError: window.matchMedia is not a function et n'exécute pas correctement le test.

Dans ce cas, simuler 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(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});

Cette solution fonctionne si window.matchMedia() est utilisé dans une fonction (ou méthode) appelée par le test. Si window.matchMedia() est exécuté directement dans le fichier testé, Jest signalera la même erreur. Dans ce cas, la solution consiste à déplacer le mock manuel dans un fichier séparé et à l'inclure dans le test avant le fichier testé :

import './matchMedia.mock'; // Must be imported before the tested file
import {myMethod} from './file-to-test';

describe('myMethod()', () => {
// Test the method here...
});