Перейти к основной части
Version: 27.1

Пользовательские имитации

Пользовательские заглушки используются для имитации функционала с помощью мок данных. Например вместо того, чтобы обращаться к удаленному ресурсу такому как веб-сайт или база данных, можно создать собственную заглушку, которая позволит использовать имитируемые данные. Это гарантирует, что тесты будет быстрыми и надежными.

Иммитация пользовательских модулей#

Файлы с имитациями пользовательских модулей помещаются в папку __mocks__/, которая должна находится в одной директории с оригинальным модулем. Например, чтобы симитировать модуль user в директории models, создайте файл user.js и поместите его в models/__mocks__. Заметьте, что название директории __mocks__ чувствительно к регистру: вариант __MOCKS__ может не сработать на некоторых системах.

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.

Имитация модулей Node#

Если вы имитируете Node модуль, (например, lodash), файл с заглушкой должен находиться в директории __mocks__ на одном уровне с node_modules (исключение: roots указывает не на корневую директорию проекта) и будет симитирован автоматически. Вызов jest.mock('module_name') не нужен.

Для модулей с пространствами имен (scoped packages) создайте подкаталог с именем пространства и поместите в него файл с заглушкой. Например, если у вас есть модуль @scope/project-name, создайте файл __mocks__/@scope/project-name.js в подкаталоге @scope/.

Warning: If we want to mock Node's core modules (e.g.: fs or path), then explicitly calling e.g. jest.mock('path') is required, because core Node modules are not mocked by default.

Примеры#

.
├── 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.

Note: In order to mock properly, Jest needs jest.mock('moduleName') to be in the same scope as the require/import statement.

Here's a contrived example where we have a module that provides a summary of all the files in a given directory. In this case, we use the core (built in) fs module.

FileSummarizer.js
'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:

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

Now we write our test. Note that we need to explicitly tell that we want to mock the fs module because it’s a core Node module:

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

The example mock shown here uses jest.createMockFromModule to generate an automatic mock, and overrides its default behavior. This is the recommended approach, but is completely optional. If you do not want to use the automatic mock at all, you can export your own functions from the mock file. One downside to fully manual mocks is that they're manual – meaning you have to manually update them any time the module they are mocking changes. Because of this, it's best to use or extend the automatic mock when it works for your needs.

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.

Использование с импортированием из ES модулей#

Если вы используете импортированные модули ES, то вам нужно размещать операторы import в верхней части файла. Но часто вам нужно поручить Jest использовать mock перед использованием модулей. По этой причине Jest будет автоматически перенаправлять вызовы jest.mock в верхнюю часть модуля (перед любым импортом). Чтобы узнать больше об этом и увидеть его в действии, см. этот репозиторий.

Моковые методы, которые не реализованы в JSDOM#

Если какой-либо код использует метод, который JSDOM (DOM реализация, используемая Jest) еще не реализован, то тестирование невозможно. Это например случай с window.matchMedia(). Jest возвращает TypeError: window.matchMedia не является функцией и неправильно выполняет тест.

В этом случае решение проблему можно решить создав mock matchMedia в тестовом файле:

Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // устарело
removeListener: jest.fn(), // устарело
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});

Это работает, если используется window.matchMedia() в функции (или методе), которая вызывается в тесте. Если window.matchMedia() выполняется непосредственно в тестируемом файле, Jest сообщает ту же ошибку. В этом случае решение состоит в том, чтобы переместить mock вручную в отдельный файл и включить его в тест до протестированного файла:

import './matchMedia.mock'; // Должен быть импортирован до тестируемого файла
import {myMethod} from './file-to-test'; // Тестируемый файл
describe('myMethod()', () => {
// Пишем наш тест тут...
});