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

Манкипатчинг JS: Продвинутое тестирование

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

Ниже пример класса, в котором метод делает запрос по сети:

import axios from 'axios';

class Api {
  // ...
  async makeRequest(url) {
    const { data } = await axios.get(url);
    this.data = data;
  }
}

Прототипная модель JS позволяет менять поведение объектов без прямого доступа к ним. Для этого достаточно заменить методы в прототипе. После этого любой объект, имеющий этот прототип, в любой части программы начнет использовать новую реализацию метода.

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

// Подменяем метод makeRequest так, чтобы он не делал сетевой запрос
// После выполнения этого кода Api меняет свое поведение не только
// в этом модуле, но и вообще по всей программе
Api.prototype.makeRequest = function() {
  console.log('no request');
  this.data = 'test data';
};

// Где-то в другом файле
// Так как объекты передаются по ссылке, то это тот же Api
// что и в коде выше
import Api from './api.js';

const client = new Api();

// Вызывается подмененный makeRequest
client.makeRequest(/* аргументы не важны, внутри они не используются */);
// => 'no request'

В тех случаях, когда объект (например, функция-конструктор) используется напрямую, все еще проще, чем с конструктором. Достаточно поменять свойство самого объекта:

Array.isArray(''); // false

// Этот код может быть вызван в любом месте программы
Array.isArray = () => true;

Array.isArray(''); // true

// То же самое касается любого импортируемого объекта
import Api from './api.js';

// Теперь везде, где будет импортироваться Api, это будет измененный Api
Api.boom = () => console.log('Hexlet Magic');

// В любом другом модуле
Api.boom(); // => 'Hexlet Magic'

Такой подход, когда глобально подменяются значения свойств, называется манкипатчинг (monkey patching). Он считается плохой практикой при написании обычного кода в JS, но он очень популярен и удобен в тестах.

Самый известный пример в JavaScript-мире — библиотека nock. С ее помощью перекрывают реальные сетевые запросы, выполняемые модулем http, который включен в стандартную библиотеку Node.js.

// Пример http-запроса с использованием модуля http
import http from 'http';

const options = {
  hostname: 'hexlet.io',
  port: 443,
  path: '/my',
  method: 'GET',
};

// request — асинхронный метод
const req = http.request(options, (res) => {
  // Тут обрабатываем http-ответ
});

Nock заменяет внутри модуля http некоторые методы, которые используются разными библиотеками для выполнения HTTP-запросов.

// Как примерно выглядит подмена

import http from 'http';

// Сохраняем старый метод
// Это позволяет вернуть его потом на место
const overriddenRequest = http.request

http.request = (/* тут такие же параметры как и у исходного метода */) => {
  // здесь логика библиотеки nock

  // Возвращаем исходный метод!
  http.request = overriddenRequest;
}

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

import nock from 'nock';
import { getPrivateForkNames } from '../src.js';

test('getPrivateForkNames', async () => {
  nock(/api\.github\.com/) // это регулярное выражение чтобы не указывать полный адрес
    // get — для GET-запросов, post — для POST-запросов и так далее
    .get(/\/orgs\/hexlet\/repos/)
    .reply(200, [{ fork: true, name: 'one' }, { fork: false, name: 'two' }]);

  const names = await getPrivateForkNames('hexlet');
  expect(names).toEqual(['one']);
});

Цепочка nock(domain).get(url) задает полный адрес страницы, запрос к которой надо перехватить. Nock анализирует все выполняемые запросы и подменяет только тот, который соответствует данным параметрам. Домен и адрес страницы могут указываться как целиком, так и через регулярное выражение, чтобы не писать слишком много.

Метод reply(code, body, headers) описывает ответ, который нужно вернуть по данному запросу. В самом простом случае достаточно указать код возврата. В нашей же ситуации кроме кода нужны данные. Именно на этих данных мы и проверяем работу функции getPrivateForkNames().

Здесь мы рассмотрели только самый базовый вариант использования Nock. У этой библиотеки огромная документация и множество вариантов использования. Полезно периодически ее просматривать в поисках более элегантных путей решения задач тестирования.

В чем плюсы и минусы такого способа работы?

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

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

В большинстве ситуаций это не так критично. Поэтому смело используйте Nock в своих проектах, но не забывайте и про другие способы.


Дополнительные материалы

  1. Кассеты для Axios

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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