Инверсия зависимостей крайне мощная техника, которая работает не только с функциями, но и с объектами. Рассмотрим ее глубже на примере 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-запрос. Прикинем, какие проблемы из-за этого могут возникнуть:
В данном примере 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-запросов из тестов является их выключение в тестах. В следующих уроках мы познакомимся с библиотекой 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"
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт