Ручные моки
Ручные моки используются для подмены функциональности тестовыми данными. Например, вместо обращения к удалённому ресурсу, такому как веб-сайт или база данных, можно создать ручной мок, позволяющий использовать фиктивные данные. Это гарантирует, что тесты будут выполняться быстро и без сбоев.
Мокирование пользовательских модулей
Ручные моки создаются путём написания модуля в подкаталоге __mocks__/, расположенном рядом с модулем. Например, чтобы создать мок для модуля user в каталоге models, создайте файл user.js и поместите его в каталог models/__mocks__.
Имена папок __mocks__ чувствительны к регистру, поэтому имя каталога __MOCKS__ может привести к сбоям в некоторых системах.
Когда мы подключаем этот модуль в тестах (то есть хотим использовать ручной мок вместо реальной реализации), необходимо явно вызвать jest.mock('./moduleName').
Мокирование модулей Node
Если модуль, для которого создаётся мок, является модулем Node (например, lodash), мок следует поместить в каталог __mocks__ рядом с node_modules (если только вы не настроили roots, указав в качестве него каталог, отличный от корня проекта). В этом случае модуль будет мокироваться автоматически. Явно вызывать jest.mock('module_name') не нужно.
Для мокирования модулей с областью видимости (также известных как пакеты с областью видимости) можно создать файл в структуре каталогов, соответствующей имени такого модуля. Например, чтобы создать мок для модуля с областью видимости @scope/project-name, создайте файл __mocks__/@scope/project-name.js, предварительно создав каталог @scope/.
Чтобы создать мок для встроенных модулей Node (например, fs или path), необходимо явно вызвать, например, jest.mock('path'), поскольку встроенные модули по умолчанию не мокируются.
Примеры
. ├── config ├── __mocks__ │ └── fs.js ├── models │ ├── __mocks__ │ │ └── user.js │ └── user.js ├── node_modules └── views
Если для модуля существует ручной мок, система модулей Jest будет использовать его при явном вызове jest.mock('moduleName'). Однако, когда automock имеет значение true, вместо автоматически созданного мока будет использоваться реализация ручного мока, даже если jest.mock('moduleName') не вызывается. Чтобы отключить это поведение, необходимо явно вызвать jest.unmock('moduleName') в тестах, где следует использовать настоящую реализацию модуля.
Чтобы мокирование работало корректно, Jest должен видеть jest.mock('moduleName') в той же области видимости, что и оператор require/import.
Рассмотрим искусственный пример: у нас есть модуль, который предоставляет сводку всех файлов в заданном каталоге. В этом случае мы используем встроенный модуль fs.
'use strict';
const fs = require('fs');
function summarizeFilesInDirectorySync(directory) {
return fs.readdirSync(directory).map(fileName => ({
directory,
fileName,
}));
}
exports.summarizeFilesInDirectorySync = summarizeFilesInDirectorySync;
Поскольку мы хотим, чтобы тесты не обращались к диску (это довольно медленно и ненадёжно), мы создаём ручной мок для модуля fs на основе автоматического мока. В нашем ручном моке будут реализованы собственные версии API fs, которые мы сможем использовать в тестах:
'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;
Теперь напишем тест. В этом случае jest.mock('fs') нужно вызвать явно, поскольку fs — встроенный модуль Node:
'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);
});
});
В показанном здесь примере мока используется jest.createMockFromModule для создания автоматического мока, поведение которого переопределяется. Это рекомендуемый, но необязательный подход. Если вы не хотите использовать автоматический мок, можно экспортировать из файла мока собственные функции. Недостаток полностью ручных моков заключается в том, что их нужно вручную обновлять каждый раз, когда меняется модуль, для которого они созданы. Поэтому, если автоматический мок подходит для ваших задач, лучше использовать его или расширить.
Чтобы ручной мок и его реальная реализация оставались согласованными, можно подключить настоящий модуль в ручном моке с помощью jest.requireActual(moduleName), дополнить его функциями-моками и экспортировать.
Код этого примера доступен в разделе examples/manual-mocks.
Использование с импортами ES-модулей
При использовании импортов ES-модулей обычно хочется размещать операторы import в начале файла теста. Однако часто необходимо указать Jest использовать мок до того, как модули начнут его использовать. Поэтому Jest автоматически поднимает вызовы jest.mock в начало модуля (перед всеми импортами). Подробнее об этом и примеры работы можно найти в этом репозитории.
Вызовы jest.mock нельзя поднять в начало модуля, если включена поддержка модулей ECMAScript. Загрузчик модулей ESM всегда выполняет статические импорты до запуска кода. Подробнее см. в разделе Модули ECMAScript.
Мокирование методов, не реализованных в JSDOM
Если в коде используется метод, который ещё не реализован в JSDOM (реализации DOM, используемой Jest), протестировать его непросто. Например, это относится к window.matchMedia(). Jest возвращает TypeError: window.matchMedia is not a function и не выполняет тест должным образом.
В этом случае проблему можно решить, создав мок для matchMedia в файле теста:
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(),
})),
});
Это работает, если window.matchMedia() используется в функции (или методе), вызываемой в тесте. Если window.matchMedia() выполняется непосредственно в тестируемом файле, Jest сообщает об этой же ошибке. В таком случае нужно переместить ручной мок в отдельный файл и подключить его в тесте до тестируемого файла:
import './matchMedia.mock'; // Must be imported before the tested file
import {myMethod} from './file-to-test';
describe('myMethod()', () => {
// Test the method here...
});
© 2022 Facebook, Inc.
Licensed under the MIT License.
https://jestjs.io/docs/manual-mocks