Мокирование внешних API

Одной из важных задач в тестировании является возможность изолировать тестируемую часть программы от внешних зависимостей. Для этого используется мокирование (mocking). В контексте тестирования с Mocha это позволяет создавать фальшивые версии сервисов или API, с которыми приложение взаимодействует, чтобы избежать реальных сетевых запросов и контролировать поведение внешних сервисов.

Зачем мокировать внешние API?

Тестирование с реальными API может быть не только медленным, но и дорогим. В случае с внешними сервисами, такие как сторонние веб-API, могут возникать ограничения по количеству запросов, а также возможные сбои в доступности. Использование моков позволяет избежать этих проблем и ускорить процесс тестирования. Мокирование внешних API позволяет:

  • Снизить зависимость от внешних сервисов, что делает тесты стабильными и независимыми.
  • Контролировать поведение внешних сервисов для тестирования различных сценариев.
  • Избегать использования реальных данных, что может быть неприемлемо по соображениям безопасности или конфиденциальности.

Инструменты для мокирования в Mocha

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

  1. Sinon.js – библиотека для создания моков, шпионов и заглушек. Она позволяет перехватывать вызовы функций, заменять реальные имплементации на фальшивые и контролировать поведение вызовов.
  2. nock – библиотека для мокирования HTTP-запросов. Она позволяет перехватывать запросы, отправляемые с использованием таких библиотек, как axios, fetch или request, и заменять их фальшивыми ответами.

Мокирование HTTP-запросов с использованием Nock

Nock — это инструмент для мокирования HTTP-запросов, который позволяет перехватывать вызовы API и подменять их заранее заданными ответами. Используя Nock, можно легко тестировать взаимодействие с внешними API, не выполняя реальные запросы.

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

const nock = require('nock');
const axios = require('axios');

describe('Тестирование API', function() {
  it('должен получить успешный ответ от внешнего API', async function() {
    // Мокируем запрос к внешнему API
    nock('https://api.example.com')
      .get('/users/1')
      .reply(200, { id: 1, name: 'John Doe' });

    // Выполняем реальный запрос, который будет перехвачен Nock
    const response = await axios.get('https://api.example.com/users/1');
    
    // Проверяем полученный результат
    response.data.should.have.property('id', 1);
    response.data.should.have.property('name', 'John Doe');
  });
});

В этом примере запрос GET /users/1 будет перехвачен Nock, и вместо реального ответа от внешнего API будет возвращен заранее заданный объект. Это позволяет тестировать логику работы с внешними API без необходимости обращаться к реальному серверу.

Мокирование внешних функций с использованием Sinon

Sinon.js предоставляет мощные инструменты для создания моков, шпионов и заглушек. С помощью мока можно заменить функцию, которая обращается к внешнему API, на фальшивую, контролируя ее поведение.

Пример мокирования функции с использованием Sinon:

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

describe('Тестирование функции с внешним запросом', function() {
  it('должна вызывать API и возвращать данные', async function() {
    const fakeResponse = { id: 1, name: 'John Doe' };
    
    // Создаем мок функции, которая будет возвращать фальшивый ответ
    const axiosGetMock = sinon.stub(axios, 'get').resolves({ data: fakeResponse });

    // Вызов функции, которая будет использовать мокированную версию axios.get
    const response = await axios.get('https://api.example.com/users/1');
    
    // Проверяем, что функция вернула ожидаемый результат
    response.data.should.have.property('id', 1);
    response.data.should.have.property('name', 'John Doe');

    // Проверяем, что мокированная функция была вызвана
    sinon.assert.calledOnce(axiosGetMock);
    
    // Восстанавливаем оригинальную функцию
    axiosGetMock.restore();
  });
});

В данном примере с помощью sinon.stub() мы создаем замену функции axios.get, которая будет всегда возвращать заранее заданный объект. Это позволяет протестировать логику обработки данных, не обращаясь к реальному API.

Различия между Nock и Sinon

  • Nock специализируется на мокировании HTTP-запросов. Он перехватывает и подменяет сетевые вызовы, возвращая заданные ответы. Это полезно, когда необходимо протестировать работу с внешними API.
  • Sinon предоставляет более общие инструменты для мокирования функций. Он позволяет заменить любые функции или методы в программе, включая те, которые делают HTTP-запросы. Это дает больше гибкости, но требует больше работы для настройки.

Тестирование различных сценариев с моками

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

  • Тестирование ошибок API. С помощью моков можно симулировать ошибки, такие как 500 серверные ошибки, ошибки сети или неправильный формат ответа.

Пример мокирования ошибки:

nock('https://api.example.com')
  .get('/users/1')
  .reply(500, { error: 'Internal Server Error' });

try {
  await axios.get('https://api.example.com/users/1');
} catch (error) {
  error.response.status.should.equal(500);
  error.response.data.should.have.property('error', 'Internal Server Error');
}
  • Тестирование асинхронных задержек. Иногда полезно проверить, как система будет вести себя при задержке в ответе от внешнего API. Nock позволяет задать задержку перед ответом.

Пример с задержкой:

nock('https://api.example.com')
  .get('/users/1')
  .delay(1000) // Задержка в 1 секунду
  .reply(200, { id: 1, name: 'John Doe' });

const startTime = Date.now();
await axios.get('https://api.example.com/users/1');
const elapsedTime = Date.now() - startTime;
elapsedTime.should.be.greaterThan(1000);

Удаление моков

Важно после каждого теста восстанавливать оригинальные функции или API, чтобы избежать побочных эффектов на другие тесты. В Sinon это делается с помощью метода restore(), а в Nock — с помощью nock.cleanAll().

afterEach(() => {
  sinon.restore(); // Восстановление всех моков
  nock.cleanAll(); // Очистка всех моков Nock
});

Заключение

Мокирование внешних API в тестах с Mocha значительно ускоряет процесс разработки и тестирования, позволяя имитировать работу с реальными сервисами без необходимости выполнения настоящих HTTP-запросов. Использование Nock и Sinon для мокирования дает гибкость и контроль над тестируемым кодом, позволяя проверять различные сценарии и гарантировать стабильность тестов.