В предыдущей части я рассказал, что такое WireMock, в каких случаях его необходимо использовать, как его разворачивать и настраивать, как правильно сопоставлять запросы с заглушками и как пользоваться журналированием. Настало время рассмотреть все что связано с HTTP ответом, который возвращает заглушка, а также затронем сценарии. Формирование ответа заглушки Тело ответа заглушки Шаблоны Переменные Функции Проксирование Эмуляция не стандартного поведения веб-сервиса Расширение Webhooks and Callbacks Сценарии Итоги В данном разделе описаны параметры объекта response, который мы немного затронули в первой части. В разделе response есть множество параметров, с ними будем разбираться постепенно, при создании первой заглушки мы столкнулись с такими параметрами как:  status – числовое статическое значение, на вход принимает значение возвращаемого статус кода; headers – объект в котором находятся заголовки ответа, в качестве ключа объекта выступает название заголовка, в качестве значения ключа выступает значение заголовка; body – строка, в которая является возвращаемым телом ответа, если туда передавать JSON или XML, то их придется экранировать. Параметры status и headers, являются простыми и с ними ничего особо не сделаешь. Вот параметр тело ответа намного интереснее. Тело ответа можно передать несколькими способами: body – его уже рассмотрели, простая строка, которая вернется в ответе; jsonBody – параметр, который принимает на вход JSON, удобен тем, что не нужно экранировать массу символов и передавать в него наглядный JSON с которым удобно работать; base64Body – строковый параметр, который принимает на вход строку в кодировке base64, удобно если требуется в ответе вернуть файл; bodyFileName – строковый параметр, который принимает на вход путь до файла, файл должен лежать на сервере в папке __files, данная папка должна находиться в директории, где расположен исполняемый файл WireMock. Итак, мы разобрались что тело запроса можно передать несколькими способами, давайте теперь попробуем разобраться с шаблонами. WireMock использует Handlebars шаблоны. Смысл их в том, что мы можем с помощью них делать динамическое тело ответа. Что бы внутрь тела ответа засунуть какую ни будь переменную или функцию их необходимо обрамить в двойные фигурные скобки. В таком случае WireMock вместо шаблона подставит необходимое значение. Отмечу что в таком случае WireMock делает экранирование спецсимволов. В некоторых случаях требуется исключить экранирование значений, которые возвращает переменная и функция, в таком случае необходимо использовать тройные фигурные скобки. Пример использования шаблонов, после дефиса идет значение, которое подставится: В WireMock есть три источника переменных По объекту request можно получить все параметры HTTP запроса, по которому была сопоставлена заглушка. Ниже описана модель объекта request: request.url – url в котором есть параметры пути и параметры запроса; request.path – url в котором есть только параметры пути; request.pathSegments.[{{n}}] – массив, в котором хранятся все параметры пути по отдельности; request.query.{{key}} – объект параметров запроса; request.query.{{key}}.[{{n}}] – массив параметров запроса, которые имеют несколько значений; request.method – метод запроса; request.host – hostname на который был отправлен запрос; request.port – порт сервера на который был отправлен запрос; request.scheme – протокол запроса; request.baseUrl – базовый URL без параметров пути и параметров запроса; request.headers.{{key}} – объект заголовков запроса; request.headers.[{{key}}] – объект заголовков запроса, используется если в названии заголовка есть спецсимволы; request.headers.{{key}}.[{{n}}] – массив значений заголовков, которые имеют несколько значений; request.cookies.{{key}} – объект cookies; request.cookies.{{key}}.[{{n}}] – массив значений cookies, которые имеют несколько значений; request.body – тело запроса. Каждую из этих переменных можно вставить в тело ответа заглушки или как аргумент к функции. Пример: В ответ данная заглушка будет возвращать подробную информацию в формате JSON о параметрах HTTP запроса. При формировании заглушки в нее можно передать локальные переменные, которые потом можно использовать в шаблонах, для этого нужно передать параметр transformerParameters, который является объектом, в качестве ключа данного объекта выступает название переменной, в качестве значения ключа выступает значение переменной. Пример использования transformerParameters: В данном случае заглушка вернет следующее тело ответа: В WireMock есть не только переменные, но и функции, которые мы рассмотрим в следующих разделах, но тут придется затронуть одну функцию, которая имеет название systemValue, она на вход принимает два аргумента type и key. Эта функция возвращает переменные окружения и свойства, которые были переданы на этапе развертывания WireMock сервера. Аргумент type принимает на вход всего два значения ENVIRONMENT и PROPERTY, для получения переменной окружения или свойства соответственно. Аргумент key принимает на вход название самой переменной. Пример использования: Одну из функций рассмотрели выше, в данном разделе подробно рассмотрим основные функции, ведь они дают огромное разнообразие для формирования тела ответа. Некоторые функции не требуют на вход аргументов, но большинство функций в них нуждается. Сами аргументы передаются в формате argument1='valueArgument1', между аргументами должен быть пробел. В качестве значения аргумента могут передаваться не только переменные, но и функции. Если в качестве значения аргумента передается функция, то она будет уже обрамлена не в двойные фигурные скобки, а в круглые скобочки. now – функция, которая возвращает текущие дату и время, без аргументов возвращает дату в формате ISO-8601 по Гринвичу. Имеет два опциональных аргумента offset и format. Первый позволяет сместить текущую дату вперед или назад на определенное значение, второй позволяет задать возвращаемый формат времени, отличный от стандарта ISO-8601. Пример: randomValue – функция, которая возвращает рандомно значения. Имеет три аргумента type, length и uppercase. Первый обязательный, определяет тип возвращаемых значений, доступны следующие типы: ALPHANUMERIC – возвращает буквы и цифры; ALPHABETIC – возвращает буквы; NUMERIC – возвращает цифры; ALPHANUMERIC_AND_SYMBOLS – возвращает буквы, цифры и символы; HEXADECIMAL – возвращает шестнадцатеричный код; UUID – возвращает идентификатор в формате UUID. Второй параметр length опциональный, указывает на размерность возвращаемой строки. Третий параметр uppercase, указывает что бы буквы возвращались в верхнем регистре.  Будьте осторожны если вы используете тип ALPHANUMERIC_AND_SYMBOLS внутри JSON, данный тип может вернуть спецсимвол, который не будет экранирован, даже если вы указали двойные фигурные скобки, что естественно поломает структуру JSON и приведет к ошибке на стороне WireMock. Пример: randomInt – функция, которая возвращает рандомно целое число, если не передавать на вход параметры, то диапазон возвращаемых значений будет от -2147483648 до 2147483647. Имеет два опциональных параметра lower и upper, которые ограничиваю диапазон возвращаемых значений. Первый ограничивает диапазон с меньшей стороны, второй с большей стороны. Оба параметра являются целыми числами. У функции randomInt есть брат randomDecimal, он имеет похожую функциональность только возвращает вещественные чиста. Диапазон возвращаемых чисел 3,4E +/- 38 (7 знаков). Соответственно аргументы являются вещественными числами, которые ограничивают диапазон возвращаемых значений. Пример: jsonPath – функция, которая позволяет вернуть значение определенного ключа (ключей) из структуры JSON используя путь JSONPath. В первой части мы уже сталкивались с JSONPath тут напомню, что это аналог XPath для JSON. Подробнее ознакомиться с JSONPath можно в данном руководстве. Функция принимает на вход два обязательных аргумента сам JSON в котором будет происходить поиск и путь JSONPath. Пример: В данном примере на вход функции jsonPath передается тело запроса, которое будет направлено в сторону заглушки. Если мы отправим запрос к заглушке со следующим телом запроса: то в ответе получим следующее тело ответа: join – функция, которая делает из массива строку, причем каждый элемент массива разделяется определенными символами. На вход принимает 2 обязательных параметра первый массив, который нужно преобразовать в строку, второй разделитель. Пример: В данном примере на вход функции join передается функция jsonPath, которая возвращает массив найденных элементов. Функция join преобразует этот массив в строку. Если мы отправим запрос к заглушке со следующим телом запроса: то в ответе получим следующее тело ответа: pickRandom – функция, которая на вход принимает множество аргументов, причем рандомно возвращает значение одного из аргументов. Также в данную функцию можно передать один массив, функция вернет рандомно один из элементов данного массива. Пример: В данном примере показаны 3 реализации функции pickRandom. В первом варианте она вернет значения 1 или 2 или 3. Во втором варианте в качестве аргумента будет передан массив данных с найденными статусами, функция вернет значение одного из статусов. В третьем варианте на вход функции передаем три других функции, pickRandom вернет рандомно значение одной из переданных функций. Пример работы pickRandom с transformerParameters: В данном примере используется локальная переменная myTestData, которая является массивом, в качестве значений выступают различные данные в виде JSON. Функция pickRandom будет возвращать рандомно один из элементов массива myTestData. lookup – функция, которая на вход принимает два параметра, объект и ключ. Результатом функции будет значение по ключу от переданного объекта. Используют в том случае если в ключе содержится спецсимвол, который может поломать синтаксис шаблонов. Также используют в том случае если значением ключа является другая переменная. Пример: Данный пример возвращает определенное значение, на определенный параметр переданный в запросе. В данном случае в функцию lookup передается параметр запроса с ключом id. В объекте transformerParameters находится набор ключей по, которым вернется определенный результат, набор может быть очень большим. Если мы в запросе передадим параметр ?id=key1, то в ответе вернется информация по пользователю Анатолий. В стандартной ситуации работа с заглушками выглядит следующим образом: В данном случае весь трафик от веб-сервиса идет на заглушку, при таком подходе можно помешать работе остальным членам команды, которые также тестируют данный инфопоток, либо используют его в каких-то других целях. В таком случает можно прибегнуть к проксированию запросов на целевой веб-сервис. Схема будет выглядеть следующим образом: В данном случае WireMock запросы, на которые не будет сопоставлена заглушка, будет перенаправлять на целевой реальный веб-сервис. Это позволит не нарушать работу остальных членов команды. Для того что бы данная схема заработала нужно использовать приоритеты заглушек. За приоритеты в WireMock отвечает параметр priority, это числовой параметр, значение которого лежит в пределе от одного до десяти. Первый приоритет является самым приоритетным, увеличиваясь он теряет свою приоритетность.  Для начала нам нужно сделать проксирование всех запросов на целевой реальный веб-сервис, и данной заглушке выставить самый низкий приоритет, за проксирование отвечает параметр proxyBaseUrl, его значением является строка с адресом на который нужно будет перенаправить запрос: В данном примере мы сделали проксирование запросов на определенный адрес, это может быть любой адрес, который вам требуется. Если мы хотим заглушить определенные данные, то необходимо сконфигурировать заглушку под эти данные, главное, чтобы приоритет у данной заглушки был выше, чем приоритет проксирующей заглушки: В данном случае, если будет запрос с параметром id равным 111, то этот запрос не будет проксирован на реальный веб-сервис, а будет заглушен. Все запросы, у которых параметр id будет отличный от 111 будут проксированы на реальный веб-сервис. За фиксированную задержку отвечает параметр fixedDelayMilliseconds, его значением является число, время задержки в миллисекундах. Пример: В данном случае WireMokc будет на каждый запрос к заглушке, ответ выдавать в течении двух секунд. За данную задержку отвечает объект delayDistribution, он может реализовать два распределения: Uniform – непрерывное равномерное распределение, данному распределению необходимо указать нижнюю и верхнюю границы, по которым будут распределятся значения. Пример uniform распределения: В данном случае время ответа заглушки будет в пределах от одной до пяти секунд, задержка будет распределяться равномерно между этими значениями. Lognormal – логнормальное распределение. Данное распределение прекрасно эмулирует поведение большей части веб-сервисов. На вход принимает два параметра, медиана и сигма. Медиана является пятидесятым перцентилем задержки, т.е. около 50% запросов будут выполняться до этого времени, остальные 50% будут тянуться длинным хвостом в большую сторону времени ответа. За длину данного хвоста отвечает параметр сигма, чем он больше, тем длиннее хвост. Само распределение можно построить на wolframalpha. Пример lognormal распределения: В данном примере, около 50% запросов к заглушке будет выполняться до двух секунд, остальные запросы будут выполняться от двух до десяти секунд, больший процент запросов будет концентрироваться на начале данного диапазона, в конце диапазона окажутся единицы. За данную задержку отвечает объект chunkedDribbleDelay, он имеет два ключа. numberOfChunks – числовой параметр, который задает на сколько частей будет разбит ответ. totalDuration – числовой параметр, который отвечает за полное время ответа. Пример: В данном примере мы разбили отдаваемый ответ заглушкой на десять частей, причем общее время ответа 5000 миллисекунд. Перед каждой отдаваемой частью ответа WireMock будет делать задержку, в данном примере эта задержка будет составлять 500 миллисекунд. Данными действиями мы можем эмулировать задержки по сетевому соединению. WireMock может эмулировать ряд нестандартных ответов, полезно при тестировании отказоустойчивости и проверки негативных сценариев. За эмуляцию не стандартных ситуаций отвечает параметр fault, это строковый параметр, который может принимать одно из четырех значений: EMPTY_RESPONSE – возвращает полностью пустой ответ; MALFORMED_RESPONSE_CHUNK – отправляет заголовок успешности ответа, затем отправляет мусор и закрывает соединение; RANDOM_DATA_THEN_CLOSE – отправляет мусор и закрывает соединение; CONNECTION_RESET_BY_PEER – некорректно закрывает TCP соединение. Пример: У WireMock есть расширение Webhooks and Callbacks, которое позволяет делать заглушки в асинхронных инфопотоках, как поднять WireMock с данным расширением, было описано в первой части. Рассмотрим пример работы асинхронного инфопотока: В данной схеме инфопоток работает следующим образом, пользователь на Frontend делает заказ, Frontend сформировал запрос в сторону Backend, в свою очередь Backend направляет запрос в сторону другого веб-сервиса на создание задания, данный веб-сервис сразу отвечает 204 кодом, и принимается выполнять задачу. После того как задача выполнена, веб-сервис направляет http запрос c результатом в сторону Backend (это и есть callback). Backend отвечает веб-сервису 204 кодом и направляет ответ с результатом в сторону Frontend. После чего пользователь получает результат выполнения задания на стороне Frontend. Пример эмуляции данного асинхронного инфопотока на стороне WireMock: В данном примере используется параметр postServeActions, в котором происходит настройка callback. Значением параметра является массив объектов, это означает что мы можем направить не один callback, а несколько, в примере отправляем один callback. Внутри объекта есть параметр parameters, он является также объектом, который содержит в себе три основных составляющих HTTP запроса, это метод, url и тело запроса. Callback обычно отправляется методом POST, в url вставляем ссылку куда нужно отправить сallback, в примере ссылку достаем из тела запроса к заглушке. Ну и также в тело запроса сallback вставляем идентификатор задачи, который пришел в теле запроса к заглушке. А чтобы callback не отправлялся сразу сделали задержку отправления от пяти до десяти секунд. Если отправим следующий запрос в сторону заглушки: то WireMock ответит 204 кодом, и через 5-10 секунд отправит следующий callback запрос: В WireMock есть функциональность сценариев, которые позволяют менять поведение заглушек в зависимости от этапов сценария. Представим ситуацию что у нас есть веб-сервис, который возвращает чеки, по совершенным ранее покупкам. У данного сервиса есть проверка на аутентификацию пользователя. Если пользователь не авторизован и попробует запросить чеки, то сервис ответит, что пользователь не авторизован, и пользователю потребуется пройти авторизацию, после чего снова запросить чеки, но на этот раз сервис вернет все запрашиваемые чеки, так как пользователь авторизован. Для работы со сценариями WireMock предоставляет три параметра: scenarioName – название сценария, по данному параметру WireMock сопоставляет все заглушки сценария. Естественно, у всех заглушек одного сценария должно быть одно и тоже название; requiredScenarioState – параметр, который указывает заглушке на каком этапе сценария нужно сработать. По умолчанию сценарий всегда начинается с состояния Started; newScenarioState – параметр, который позволяет определенной заглушке изменить состояние сценария, если произойдет вызов данной заглушки. Давайте сделаем следующий сценарий: Получаем чеки – возвращается ответ не авторизован; Проходим авторизацию – авторизация проходит успешно; Получаем чеки – возвращается ответ с чеками; Производим операцию logout – logout проходит успешно; Получаем чеки – возвращается ответ не авторизован. Для данного сценария необходимо сделать четыре заглушки: Первая заглушка, получение чеков, когда пользователь не авторизован: Вторая заглушка, пользователь авторизуется. Данная заглушка меняет состояние сценария на авторизован: Третья заглушка, получение чеков, когда пользователь авторизован, данная заглушка схожа по сопоставлению с первой, но реагирует на другой этап сценария: Четвертая заглушка, операция logout, происходит смена состояния сценария на не авторизован: Теперь пройдемся по сценарию, описанному выше: Делаем запрос GET /check, ответ приходит от первой заглушки, не авторизован, так как сценарий в WireMock находится на этапе Started; Делаем запрос POST /auth, ответ приходит от второй заглушки, также происходит смена этапа сценария со Started в Authorized; Делаем запрос GET /check, ответ приходит от третьей заглушки, в ответе пришли чеки, так как сценарий в WireMock находится на этапе Authorized; Делаем запрос POST /unauth, ответ приходит от четвертой заглушки, также происходит смена этапа сценария с Authorized в Started; Делаем запрос GET /check, ответ приходит от первой заглушки, не авторизован, так как сценарий в WireMock находится на этапе Started. Принцип сценариев прост, главное понять суть трех новых параметров, которые мы рассмотрели в данном разделе. Если комбинировать сценарии с приоритетами заглушек, то можно делать более сложные кейсы. WireMock это мощный инструмент, с помощью которого можно воспроизвести практически любое поведение HTTP веб-сервиса, причем для настройки заглушек не требуется знаний языков программирования, а само API настройки заглушек обладает широкой функциональностью, которую легко и быстро реализовать на практике. WireMock также прекрасно подходит для работы со сценариями в нагрузочном тестировании, на своем опыте убедился что он выступает скалой, которая устоит под любым шквалом запросов, главное правильно его настроить. С использованием заглушек у тестировщиков, системных аналитиков и разработчиков сильно развязываются руки, позволяя делать больше не стандартных кейсов Отмечу что WireMock можно использовать не только в ручном и нагрузочном тестировании, но и в автоматизации. Есть множество кейсов использования WireMock в автоматизации, но это тема совершенно другой статьи.  QA Lead