Мокирование модулей

Mocha предоставляет гибкие возможности для тестирования кода на JavaScript, включая возможность мокирования зависимостей. Мокирование используется для имитации поведения внешних модулей или компонентов, которые могут быть сложными для тестирования или имеют побочные эффекты. Это позволяет сосредоточиться на тестировании логики, не затрагивая реальных зависимостей. В Mocha для мокирования часто применяются такие библиотеки, как Sinon.js, однако возможны и другие подходы.

Что такое мокирование?

Мокирование (mocking) — это процесс замены реальных объектов или функций их фиктивными версиями, которые имитируют поведение оригинала. В отличие от стубов (stubs), которые просто имитируют вызовы функций, моки позволяют задавать ожидаемое поведение и проверять, как они используются в тестах.

Зачем использовать мокирование?

Мокирование необходимо для:

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

Мокирование с помощью Sinon.js

Для мокирования в Mocha часто используется библиотека Sinon.js, которая предлагает несколько инструментов: стабы, моки, шпионы.

Установка и настройка Sinon.js

Для использования Sinon.js в проекте, его необходимо установить через npm:

npm install sinon --save-dev

После этого можно подключить Sinon в файлах тестов:

const sinon = require('sinon');

Мокирование функций

Для создания мока используется метод sinon.mock(). Моки позволяют задавать ожидаемое поведение и проверять, как были использованы функции.

Пример:

const sinon = require('sinon');

describe('Мокирование с Sinon', function () {
  it('должен проверять, был ли вызван метод', function () {
    const myObj = {
      myMethod: function () {
        return 'Hello, world!';
      }
    };

    const mock = sinon.mock(myObj);
    mock.expects('myMethod').once().returns('Mocked value');

    myObj.myMethod(); // Вызов метода

    mock.verify(); // Проверка, что метод был вызван
  });
});

В этом примере создается мок для метода myMethod, который ожидает его вызов один раз и возвращает подмененное значение. Метод mock.verify() проверяет, что все ожидания были выполнены.

Мокирование асинхронных функций

Для асинхронных функций моки могут ожидать промисы или другие асинхронные действия. Например:

const sinon = require('sinon');

describe('Мокирование асинхронных функций', function () {
  it('должен правильно мокировать асинхронную функцию', async function () {
    const myObj = {
      fetchData: function () {
        return new Promise((resolve) => resolve('Real data'));
      }
    };

    const mock = sinon.mock(myObj);
    mock.expects('fetchData').once().returns(Promise.resolve('Mocked data'));

    const result = await myObj.fetchData();

    mock.verify();
    assert.equal(result, 'Mocked data');
  });
});

В этом примере асинхронная функция fetchData возвращает мокированный результат вместо настоящего.

Мокирование импортированных модулей

Часто бывает необходимо мокировать не просто методы объектов, а целые модули, которые импортируются в тестируемую единицу. В этом случае используется функциональность библиотеки proxyquire или встроенные средства, такие как require.cache.

Мокирование с использованием proxyquire

proxyquire позволяет заменять зависимости в процессе выполнения кода. Это полезно для мокирования импортированных модулей.

Установка:

npm install proxyquire --save-dev

Пример использования:

const proxyquire = require('proxyquire');
const sinon = require('sinon');
const assert = require('assert');

describe('Мокирование модулей с Proxyquire', function () {
  it('должен мокировать зависимость в модуле', function () {
    const myDependency = sinon.stub().returns('Mocked value');
    const myModule = proxyquire('./myModule', {
      './myDependency': myDependency
    });

    assert.equal(myModule(), 'Mocked value');
    sinon.assert.calledOnce(myDependency);
  });
});

Здесь мы используем proxyquire для замены реального модуля myDependency на мок, который возвращает заранее заданное значение.

Мокирование с использованием require.cache

Мокирование модуля также возможно путем замены кэшированных версий модулей в require.cache. Это позволяет изменять поведение модуля для тестов, не изменяя исходный код.

Пример:

const requireCache = require.cache;
const sinon = require('sinon');
const assert = require('assert');

describe('Мокирование с require.cache', function () {
  it('должен мокировать модуль через require.cache', function () {
    const originalModule = require('./myModule');
    const mock = sinon.stub().returns('Mocked value');

    // Мокируем модуль в require.cache
    requireCache[require.resolve('./myModule')] = {
      exports: mock
    };

    const result = require('./myModule');
    assert.equal(result(), 'Mocked value');
  });
});

Здесь мы изменяем кэшированный модуль myModule, заменяя его мокированным вариантом.

Мокирование сторонних библиотек

Для мокирования сторонних библиотек можно использовать те же методы, что и для обычных зависимостей. Это особенно полезно при тестировании кода, который зависит от таких библиотек, как axios, lodash и других.

Пример мокирования библиотеки axios с использованием Sinon.js:

const axios = require('axios');
const sinon = require('sinon');
const assert = require('assert');

describe('Мокирование axios', function () {
  it('должен мокировать запросы axios', async function () {
    const mock = sinon.stub(axios, 'get').resolves({ data: 'Mocked data' });

    const response = await axios.get('https://example.com');
    
    assert.equal(response.data, 'Mocked data');
    mock.restore(); // Восстановление оригинальной функции
  });
});

В данном примере используется метод sinon.stub() для замены метода axios.get() на мок, который всегда возвращает заданные данные.

Проверка вызовов и аргументов

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

Пример:

const sinon = require('sinon');
const assert = require('assert');

describe('Проверка вызовов с Sinon', function () {
  it('должен проверять количество вызовов и аргументы', function () {
    const myFunc = sinon.stub();
    myFunc('argument1', 'argument2');
    
    sinon.assert.calledOnce(myFunc);
    sinon.assert.calledWith(myFunc, 'argument1', 'argument2');
  });
});

Здесь мы проверяем, что функция была вызвана один раз с конкретными аргументами.

Заключение

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