Иногда ваше тестовое окружение не готово к изоляции от внешних сервисов. Более того, тестовый фреймворк не готов тоже. Но наступает момент, когда вы понимаете, что это нужно изменить и размышляете, как  наиболее плавно перейти на моки. В этой статье я хочу рассказать о нескольких вариантах организации мокирования веб-сервисов в подобном случае, о том, как связать это с автоматизацией тестирования и о нашем опыте внедрения Mockserver в стек инструментов компании. За время работы автоматизатором в разных компаниях я наблюдаю типичную эволюцию развития тестового окружения. Практически всегда она начинается с одного девелоперского стенда, дальше появляется стенд для тестирования релизных сборок, потом возникает запрос на увеличение количества тестовых серверов для разных смежных команд, у них появляются свои и так далее. И через какое-то время со всей этой сетью становится довольно сложно работать. В какой-то момент выясняется, что тестовый стенд смежной команды очень сильно влияет на стабильность ваших тестовых прогонов и возникает желание максимально изолироваться от влияния сторонних сервисов. Более того, для автоматизации некоторых тестов бывает необходимо подготовить соответствующие ответы этих сервисов, а это не всегда осуществимо. Хороший пример, когда нужно проверить, как приложение реагирует на 500 ошибку внешнего сервиса, но ты не имеешь возможности заставить сторонний сервер вернуть тебе такую. И в итоге автоматизаторы и разработчики используют подмену (мокирование) сторонних запросов для получения нужной изоляции и гибкости тестирования и разработки. Если в компании тестовое окружение развивалось по вышеописанному сценарию, то и тестовый фреймворк с большим количеством тестов уже скорее всего написан и сильно зависит от текущей тестовой инфраструктуры и ее связанности с внешними сервисами. В тот момент, когда команда решает изолировать свою тестовую инфраструктуру,  и возникает вопрос как подружить тесты с мок-сервисами. Я бы выделил два варианта  в организации связи тестов и моков: Когда используется какой-нибудь инструмент мокирования запросов, умеющий отвечать на запрос по какому-то специальному правилу, заданному извне. Таких инструментов уже достаточно много, и mockserver, о котором речь пойдет ниже, один из них. Когда есть самописный сервис, почти полностью имитирующий внешний в нужных нам точках взаимодействия. Оба эти варианта имеют право на жизнь, могут сочетаться в одном проекте и у обоих есть как достоинства, так и недостатки. Давайте рассмотрим их по отдельности. Для этого потребуется развернуть какой-то инструмент мокирования в тестовом окружении и перенаправить тестируемые сервисы на него. Далее в тестовом фреймворке нужно будет создать все необходимые генераторы эмулируемых данных, написать клиента к этому сервису и расставить методы, которые создают необходимые ответы в сервисе моков, по имеющимся тестам и их шагам. Например, нам нужно написать тест на создание пользователя, в процессе которого подгружаются данные из сторонней информационной адресной системы или микросервиса. И этот сторонний сервис нам и надо эмулировать. Итоговый результат в псевдо-коде может выглядеть так: Данный вариант позволит описать в каждом тесте всю логику поведения не только тестируемой системы, но и сторонних внешних сервисов, так как мы явно видим и задаем данные, которые эти сервисы должны возвращать. Это улучшает наглядность теста и добавляет гибкости создания предусловий. Но есть и минусы. Для того, чтобы эмулировать что-то посложнее, чем пример выше, нужно будет предварительно создавать ожидания сразу в нескольких сервисах и правильно синхронизировать данные между ними. Это добавляет сложности в написании тестовых предусловий и приводит к разрастанию самого теста. Например: Еще один минус заключается в том, что если соседняя команда тоже хотела бы использовать ваши наработки по мокированию сервисов, но использует другой язык разработки, то она не сможет легко и просто подключить вашу библиотеку для работы с генератором ожиданий и придется изобретать что-то свое. В этом случае нам надо повторить логику эмулируемой системы во вспомогательном, зачастую самописном, сервисе. Это потребует дополнительных затрат времени разработчиков по созданию такого сервиса и дальнейшей поддержке, но при этом никаких особых изменений в тестовом фреймворке не потребуется. Плюс еще и в том, что подобный сервис мокирования может быть использован смежными командами. Неудобство в том, что для добавления какой-то особой логики, необходимой тестам, придется делать изменения в самом коде сервиса, пересобирать, разворачивать и так далее. В нашей команде возникло желание избавиться от зоопарка существующих заглушек и добавить гибкости нашим тестам. Другими словами, хотелось собрать все плюсы вышеперечисленных вариантов в одном инструменте, который будет легче поддерживать команде, хорошо держит нагрузку и позволяет всем участникам легко и просто мокировать необходимые запросы. И mockserver решает все эти задачи. Когда тестировщику нужно создать необходимый ответ на запрос (expectation), он использует удобное API mockserver’a: В примере выше создаётся правило “верни мне ответ 200 c телом "response_body" на GET запрос по пути /service/path”. То есть в нужный момент выполнения теста, перед каким-то шагом, я могу создать такое ожидание и тестируемая система получит нужный мне ответ. Очень удобно при тестировании особых наборов данных, кодов ошибок и так далее, то есть всего того, что сложно или невозможно эмулировать на реальном сервисе. Более того, у mockserver есть интересная возможность создания динамических callback-ов, которые позволяют выполнить необходимый нам код во время выдачи ответа на запрос. Другими словами, я могу не только сгенерировать какой-то ответ динамически, например, создав случайный идентификатор, но и осуществить запросы в базу, другой сервис и так далее, то есть выполнить любую логику. Я даже могу создать новое ожидание в mockserver! А ведь это, по сути, позволяет нам создать “умный” мок на готовой платформе, описав минимумом кода только нужную часть эмулируемой логики. Например, у нас есть задача по созданию сервиса, эмулирующего взаимодействие с системой управления пользователями. Концепт решения может выглядеть так: Каждый тест атомарен и создает нового пользователя запросом POST /api/v1/user в систему, с передачей параметров пользователя в payload-е запроса. Вдобавок к пользователю будет создаваться account и привязываться к нему. Реакцией на такой запрос POST будет выполнение необходимого нам кода - обработка переданных параметров пользователя, генерация уникального ID пользователя и создание нужных ответов в mockserver для будущих запросов тестируемой системы. Подготовка ожиданий (нужного ответа) на будущие GET запросы по адресам /api/v1/users/{user_uid} /api/v1/users/{user_uid}/accounts/{account_id} Давайте более подробно на примере разберем, как это можно сделать. В момент старта mockserver’а мы можем указать ему подключить собранные JAR файлы нашего проекта в свой classpath, что и позволит нам использовать наш код. Ниже пример того, как создать такой класс UserHandleExpectation, наш будущий "умный" мок, пошагово: Добавляем зависимости в pom.xm  В настройках билда проекта артефакты настраиваем так, чтобы все зависимости собирались в отдельные JAR файлы, и сам скомпилированный код классов тоже был в JAR архиве. Это важно, так как иначе вы будете получать ошибку ClassNotFoundException при попытке использования своего ожидания. Создаем package userApi и внутри класс-файл UserHandleExpectation.kt Сопутствующий код для работы примера вы найдете по ссылке smart mock example Собираем проект и запускаем mockserver с помощью docker следующей командой: Здесь монтируемая папка /libs должна содержать все скомпилированные JAR файлы проекта. Для того, чтобы получить точку входа, когда наш мок отреагирует, а это в нашем случае запрос POST /api/v1/users, я должен создать ожидание в mockserver. Выполняем команду:   Перейдя на дашборд mockserver по адресу http://localhost:1080/mockserver/dashboard, мы видим следующую картину: Посылаем POST запрос для генерации UUID пользователя и его аккаунта, а также создания ответов для будущих GET запросов:   Ответ: И на дашборде отображаются наши новые ожидания: Таким образом, развивая эту концепцию, можно создать "умный" мок на базе mockserver, который будет сам готовить необходимые ответы на все необходимые эндпоинты на основе ваших входных данных. После реализации такого мока мы решили протестировать его под нагрузкой, чтобы проверить, как быстро он обрабатывает динамические callback-и и создает ожидания. Оказалось, что в ряде случаев, когда нам необходимо создать несколько ожиданий за обработку одного callback-а, mockserver иногда подвисает и отдает 404 ответ на запрос после тайм аута в 20 секунд. Первым делом поигрались с настройками. Привожу их ниже:  Также не пожалейте mockserver памяти и CPU. Памяти лучше выделить около 1 гигабайта и несколько процессорных ядер, если вы планируете запускать тесты больше чем в 16 потоков и активно использовать динамические callback-и Из всех настроек лучше всего помогает mockserver.matchersFailFast=true Эта настройка на первом же несовпадении дает ответ, что ожидание не подошло. В нашем случае, когда мы сравниваем только по path, это некритично. Тюнинг настроек улучшил ситуацию, но не избавил от проблем полностью. После безуспешных поисков решения мы перешли на использование http клиента к mockserver’у  вместо использования нативного Java клиента как в примере выше. Плюс ко всему, mockserver только через JSON REST API поддерживает создание пакета ожиданий за один запрос, а это оказалось удобно и полезно для снижения нагрузки. Добавляем okktp библиотеку в pom.xml проекта   Создаем отдельный класс HTTP клиента  И с учетом этого клиента создание ожиданий в коде класса callback теперь выглядит так:    После всех действий мы получаем удобный инструмент, который сам на своей стороне занимается созданием необходимых ответов на наши последующие вызовы, и бонусом - имеем дополнительную гибкость при создании предусловий в нашем тестовом фреймворке. Ведь теперь мы можем в нужных тестах просто изменить уже подготовленное моком ожидание напрямую. Например, мок подготовил нам серию ответов, но на каком-то шаге теста мы хотим получить особое значение параметра. Тогда мы запрашиваем из mockserver это ожидание, изменяем в нем этот параметр, обновляем ожидание и тест получает нужные нам данные. В итоге, нет нужды в каждом тесте прописывать создание типичных предусловий, этим занимается сам мок. Благодаря возможностям mockserver мы смогли реализовать на его платформе удобный и универсальный инструмент мокирования запросов, который дает возможность гибко управлять данными во время выполнения тестов, и в то же время позволяет вынести логику сторонних сервисов отдельно. Все возможности mockserver, способы матчинга запросов, их верификацию и многое другое вы сможете найти в подробной документации. Код из примеров выше вы сможете найти по ссылке.  QA