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
Manual mocks are defined by writing a module in a __mocks__/ subdirectory immediately adjacent to the module. For example, to mock a module called user in the models directory, create a file called user.js and put it in the models/__mocks__ directory.
The __mocks__ folder is case-sensitive, so naming the directory __MOCKS__ will break on some systems.
When we require that module in our tests (meaning we want to use the manual mock instead of the real implementation), explicitly calling jest.mock('./moduleName') is required.
Simulation des modules Node
If the module you are mocking is a Node module (e.g.: lodash), the mock should be placed in the __mocks__ directory adjacent to node_modules (unless you configured roots to point to a folder other than the project root) and will be automatically mocked. There's no need to explicitly call jest.mock('module_name').
Scoped modules (also known as scoped packages) can be mocked by creating a file in a directory structure that matches the name of the scoped module. For example, to mock a scoped module called @scope/project-name, create a file at __mocks__/@scope/project-name.js, creating the @scope/ directory accordingly.
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
When a manual mock exists for a given module, Jest's module system will use that module when explicitly calling jest.mock('moduleName'). However, when automock is set to true, the manual mock implementation will be used instead of the automatically created mock, even if jest.mock('moduleName') is not called. To opt out of this behavior you will need to explicitly call jest.unmock('moduleName') in tests that should use the actual module implementation.
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é. In this case, we use the core (built in) fs module.
'use strict';
const fs = require('fs');
function summarizeFilesInDirectorySync(directory) {
return fs.readdirSync(directory).map(fileName => ({
directory,
fileName,
}));
}
exports.summarizeFilesInDirectorySync = summarizeFilesInDirectorySync;
Since we'd like our tests to avoid actually hitting the disk (that's pretty slow and fragile), we create a manual mock for the fs module by extending an automatic mock. Our manual mock will implement custom versions of the fs APIs that we can build on for our tests:
'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;
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("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);
});
});
The example mock shown here uses jest.createMockFromModule to generate an automatic mock, and overrides its default behavior. 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.
To ensure that a manual mock and its real implementation stay in sync, it might be useful to require the real module using jest.requireActual(moduleName) in your manual mock and amending it with mock functions before exporting it.
The code for this example is available at examples/manual-mocks.
Utilisation avec les importations de modules ES
If you're using ES module imports then you'll normally be inclined to put your import statements at the top of the test file. Mais il est souvent nécessaire d'indiquer à Jest d'utiliser une simulation avant que les modules ne l'utilisent. For this reason, Jest will automatically hoist jest.mock calls to the top of the module (before any imports). To learn more about this and see it in action, see this repo.
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. This is e.g. the case with window.matchMedia(). Jest returns TypeError: window.matchMedia is not a function and doesn't properly execute the test.
In this case, mocking matchMedia in the test file should solve the issue:
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(),
})),
});
This works if window.matchMedia() is used in a function (or method) which is invoked in the test. If window.matchMedia() is executed directly in the tested file, Jest reports the same error. In this case, the solution is to move the manual mock into a separate file and include this one in the test before the tested file:
import './matchMedia.mock'; // Must be imported before the tested file
import {myMethod} from './file-to-test';
describe('myMethod()', () => {
// Test the method here...
});