Spec-Zone.ru › Jest

Руководство по ручным эмуляциям

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

Эмуляция модулей пользователя

Ручные эмуляции определяются путем написания модуля в подкаталоге __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') является обязательным, так как ядровые модули Node не эмулируются по умолчанию.

Примеры

.
├── 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;
FileSummarizer.js

Поскольку мы хотим, чтобы наши тесты избегали фактического доступа к диску (это довольно медленно и ненадёжно), мы создаем ручную эмуляцию для модуля 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;
__mocks__/fs.js

Теперь мы пишем наш тест. Обратите внимание, что нам нужно явно указать, что мы хотим эмулировать модуль 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);
  });
});
__tests__/FileSummarizer-test.js

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

Чтобы гарантировать, что ручная эмуляция и ее реальная реализация останутся синхронизированными, может быть полезно потребовать реальный модуль, используя jest.requireActual(moduleName) в вашей ручной эмуляции и дополнить его эмуляционными функциями перед экспортом.

Код этого примера доступен по адресу examples/manual-mocks.

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

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

Моделирование методов, которые не реализованы в 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API