Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Тестирование HTTP-запросов JS: Продвинутое тестирование

Инверсия зависимостей крайне мощная техника, которая работает не только с функциями, но и с объектами. Рассмотрим ее глубже на примере HTTP-запросов и познакомимся с таким понятием как заглушка (стабинг, stub).

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

// Библиотека для работы с GitHub API
import { Octokit } from '@octokit/rest';

const getPrivateForksNames = async (username) => {
  const client = new Octokit();
  // Клиент выполняет запрос на гитхаб и возвращает список приватных репозиториев указанной организации
  // Данные хранятся в свойстве data возвращаемого ответа
  const { data } = await client.repos
    .listForOrg({
      username,
      type: 'private',
    });
  // Оставляем только имена форков
  return data.filter(repo => repo.fork).map(repo => repo.name);
};

Давайте ее протестируем. Что мы хотим от этой функции? В первую очередь убедиться, что она работает правильно — возвращает массив приватных форков. Идеальный тест выглядел бы так:

test('getPrivateForksNames', async () => {
  const privateForks = await getPrivateForksNames('hexlet');
  expect(privateForks).toEqual([/* массив имен, которые мы ожидаем увидеть */]);
});

К сожалению, не все так просто. Внутри функции выполняется HTTP-запрос. Прикинем, какие проблемы из-за этого могут возникнуть:

  1. Нестабильная сеть может тормозить выполнение тестов и приводить к фантомным ошибкам. Тесты будут иногда проходить, иногда нет
  2. У сервисов подобных github.com установлены ограничения на запросы в секунду, в час, день и так далее. Со 100% вероятностью тесты начнут упираться в эти лимиты. Более того, есть шанс, что машина с которой идут запросы, будет заблокирована
  3. Реальные данные на GitHub не статичны, они могут и, скорее всего, будут меняться, что опять же приведет к ошибкам и необходимости править тесты

В данном примере HTTP-запрос воспринимается как помеха к тому, чтобы протестировать нашу основную логику. Мы доверяем github.com и его библиотеке @octokit/rest, то есть нам не нужно проверять, что она работает правильно (иначе можно свихнуться, если не доверять никому).

Из предыдущего урока мы узнали о нескольких способах выхода из этой ситуации и теперь можем применить один из них.

Инверсия зависимостей

Для использования инверсии зависимости добавим вторым аргументом функции сам клиент Octokit. Это позволит подменить его в тестах:

import { Octokit } from '@octokit/rest';

// Библиотека передается снаружи и ее можно подменить
const getPrivateForksNames = async (username, client = new Octokit()) => {
  // ...
};

Нам придется реализовать фейковый (ненастоящий) клиент, который ведет себя примерно так же, как и реальный Octokit, за исключением того, что он не выполняет сетевых запросов. Также нам нужно описать конкретные данные, которые вернет вызов listForOrg. Только в таком случае мы сможем протестировать, что функция getPrivateForksNames() работает корректно.

// Структура этого класса описывает только ту часть,
// которая необходима для вызова await client.repos.listForOrg(...)
class OctokitFake {
  // Здесь мы описываем желаемые данные, которые вернутся в тесте
  constructor(data) {
    this.data = data;
  }

  repos = {
    listForOrg: () => {
      // Структура возврата должна совпадать с реальным клиентом
      // Только в этом случае получится прозрачно подменить реальный клиент на фейковый
      return Promise.resolve({ data: this.data }); // так как метод асинхронный
    },
  }
}

И сам тест с использованием этого клиента:

import OctokitFake from '/OctokitFake.js';

test('getPrivateForksNames', async () => {
  const data = /* ответ от GitHub, который мы хотим проверить */;
  const client = new OctokitFake(data);
  // Внутри выполняется запрос, который возвращает сформированные выше данные
  const username = /* имя пользователя на гитхабе */;
  const privateForks = await getPrivateForksNames(username, client);
  expect(privateForks).toEqual(/* что мы ожидаем основываясь на том, что вернул listForOrg */);
});

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

Запрет HTTP-запросов

Другим способом предотвращения HTTP-запросов из тестов является их выключение в тестах. В следующих уроках мы познакомимся с библиотекой Nock, имеющей метод для запрета любых HTTP-запросов из кода: nock.disableNetConnect(). Рекомендуется вызывать его в начале файла с тестами. Помимо этого, он помогает увидеть, на какие страницы выполняют запросы сторонние библиотеки. Вот как выглядит вывод после выключения внешних соединений (при условии, что не выполнялась подмена запросов):

    HttpError: request to https://api.github.com/orgs/hexlet/repos?type=private failed, reason: Nock: Disallowed net connect for "api.github.com:443/orgs/hexlet/repos?type=private"

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 25 апреля
профессия
от 9 900 ₽ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 25 апреля
профессия
от 6 300 ₽ в месяц
Разработка бэкенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 25 апреля
профессия
новый
Автоматизированное тестирование веб-приложений на JavaScript
8 месяцев
c опытом
в разработке
Старт 25 апреля

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»