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

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

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

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

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

note

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

caution

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.

Примеры

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

info

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. 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("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 в верхнюю часть модуля (перед любым импортом). Чтобы узнать больше об этом и увидеть его в действии, см. этот репозиторий.

caution

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.

Моковые методы, которые не реализованы в 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()', () => {
// Пишем наш тест тут...
});