Модульные тесты жизненно важны в разработке программного обеспечения. Особенно при создании большого приложения мы хотим убедиться, что все различные модули работают должным образом.

Модульные тесты по определению помогают нам гарантировать, что отдельная единица кода делает то, что должна делать. Следовательно, _чтобы создать хорошие модульные тесты, мы должны убедиться, что мы максимально изолируем фрагмент кода, который хотим протестировать_; мы не хотим, чтобы другие модули (необходимые зависимости) мешали результатам модуля, который мы тестируем.

Однако может быть немного сложно имитировать каждую зависимость, которую требует модуль. Вот где __Proxyquire__ вступает в действие.

__Proxyquire__ — это удобный модуль, который _позволяет нам проксировать все требования модуля при его импорте_. Если мы объединим его с __Sinon__ (еще один полезный модуль Node JS для тестирования), мы теперь сможем легко заглушить любую зависимость, необходимую для тестируемого модуля, что позволяет легко сосредоточиться на тестировании его поведения изолированно.

## Как это работает

Давайте используем пример, чтобы проиллюстрировать, как __Proxyquire__ и __Sinon__ работают вместе. Представьте, что у нас есть большое приложение, и нам нужен модуль для извлечения/сохранения пользовательских настроек из/в файл. Давайте создадим этот модуль вместе с соответствующим модульным тестом.

Код для этого модуля диспетчера настроек будет выглядеть следующим образом (проверки и обработка ошибок для простоты опущены):

```
const fs = require('fs/promises');

module.exports = () =› {
const load = async () =› {
try {
const rawSettings = await fs.readFile('settings.json');
return JSON.parse(rawSettings);
} catch (ошибка) {
return {};
}
};

const save = async (settings = {}) =› {
const settingsStr = JSON.stringify(settings);
fs.writeFile(‘settings.json’, settingsStr);
};

return {
загрузить,
сохранить
};
};
```

Как мы видим, наш новый модуль опирается на встроенный в Node JS модуль _file system_. Следовательно, поведение этого внешнего модуля может повлиять на модульные тесты только что созданного модуля диспетчера настроек. В идеале мы не хотим, чтобы это произошло, потому что: 1) поведение модуля _fs_ (или любого другого модуля, который может потребоваться нашему тестовому образцу) может быть полностью от нас не зависит (скажем, соединения с внешними API, базами данных, файловыми системы и т. д.) и 2) мы хотим сосредоточить наши модульные тесты на одном модуле (а не на его зависимостях).

Итак, давайте посмотрим, как мы можем провести модульное тестирование нашего модуля, имитируя его зависимости и избегая нежелательного поведения.

(Примечание. В этом примере мы будем использовать __Mocha__ + __Chai__ в качестве тестовой среды + библиотеки. Затем мы добавим __Sinon__ и __Proxyquire__ для имитации зависимостей модуля диспетчера настроек и протестируем его изолированно.)

Перво-наперво: чтобы установить все зависимости для разработчиков, которые мы собираемся использовать для тестирования, мы можем использовать следующую команду (или установить каждую зависимость по отдельности):

```
npm install mocha chai sinon sinon-chai proxyquire
```

Затем идет код (некоторые тестовые примеры опущены для простоты):

```
const chai = require('chai');
chai.use(require('sinon-chai'));
const { expect } = chai;
const sinon = require('sinon');
const proxyquire = require('proxyquire').noCallThru();

description(‘Диспетчер настроек’, () =› {
const settingsPath = ‘settings.json’;
let settingsFake;
let fsFake;
let settingsManager;

beforeEach(() =› {
settingsFake = {
language: ‘English’
};

// [1] Создать поддельный модуль 'fs' с помощью Sinon.
fsFake = {
// [2] Заглушить функцию 'readFile'.
readFile: sinon.stub(). callFake(path =› JSON.stringify(settingsFake)),
// [3] Заглушка функции 'writeFile'.
writeFile: sinon.stub().callsFake((path, settings) =› {
settingsFake = JSON.parse(settings)
})
};

// [4] Загрузите модуль диспетчера настроек с помощью Proxyquire.
settingsManager = proxyquire('../../settingsManager', {
'fs/promises': fsFake
})( );
});

it(‘загружает настройки’, async() =› {
const settings = await settingsManager.load();

// Проверяем правильность вызова функции-заглушки.
expect(fsFake.readFile).to.have.been.callOnceWith(settingsPath);
// Проверяем правильность возвращенных настроек.
ожидать(настройки).чтобы.было('объект').что.глубоко.равно(настройкиФальшивые);
});

it('сохраняет настройки', async() =› {
const defaultSettings = {
language: 'English'
};
const updatedSettings = {
language: 'Японский'
};
const updatedSettingsStr = JSON.stringify(updatedSettings);

// Во-первых, проверьте, что исходные настройки равны настройкам по умолчанию.
let settings = await settingsManager.load();
expect(settings).to.be.an('object'). that.deep.equals (Настройки по умолчанию);

// Затем обновите настройки…
await settingsManager.save(updatedSettings);
// ..и проверьте правильность вызова функции-заглушки.
expect(fsFake.writeFile).to .have.been.callOnceWith(settingsPath, updatedSettingsStr);

// Наконец, проверьте правильность обновления настроек.
settings = await settingsManager.load();
expect(settings).to.be.an('object').that.deep.equals (updatedSettings);
});
});
```

Вот краткое объяснение того, что происходит:

Во-первых, мы создаем поддельный модуль «_fs_» с __Sinon__ (1), который содержит заглушенные версии функций, которые мы используем в нашем модуле. У нас есть заглушенная функция _readFile_ (2) с упрощенным поведением: она получает путь в качестве параметра (который полностью игнорируется) и просто возвращает поддельные строковые настройки; и у нас также есть заглушенная функция ‘_writeFile_’ (3) с упрощенным поведением: она получает путь и настройки для сохранения в качестве параметров и просто обновляет поддельные настройки с проанализированными настройками. Мы не тестируем ни одну из этих функций модуля «_fs_», поэтому мы можем упростить их поведение, поскольку мы ожидаем, что наш модуль будет работать правильно.

Наконец, мы загружаем наш модуль диспетчера настроек с помощью __Proxyquire__ (4), чтобы внедрить заглушенные зависимости, предотвращая вместо этого загрузку реальных.

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

Вот как мы можем имитировать зависимости для создания модульных тестов в Node JS с помощью __Proxyquire__ и __Sinon__!

Спасибо за прочтение!

Слова Рикардо Мендосы, главного инженера Altimetrik

Материалы:
- [Репозиторий Proxyquire на Github] (https://github.com/thlorenz/proxyquire)
- [Документация Sinon] (https://sinonjs.org/releases/latest/)