Задача
Разработать мок для проверки асинхронного обмена сообщениями с внешней системой.
Как пример, рассмотрим некий кейс проверки валидности промокода внешней системой. По шагам:
1) Отправляем запрос в сервис внешней системы;
Запрос PUT содержит поля (например, в headers):
promotionalCode=correctPromotionalCode
correlationId=рандомная строка, которую нужно будет вернуть в callback
replyTo= адрес сервиса принимающего callback
2) Внешняя система отвечает синхронно - 204 No content;
3) Через 5 секунд внешняя система отправляет асинхронный callback (по адресу replyTo из запроса):
Запрос POST с телом сообщения:
status = "success", если promotionalCode запроса равен "correctPromotionalCode", в остальных случаях - "validationError"
correlationId = correlationId из запроса
Решение
Для решения данной задачи используем SoapUI. Данный инструмент достаточно гибок в плане создания "умных" моксервисов, тк позволяет написать скрипты, как на этапах запуска/остановки моксервиса (Start Script/ Stop Script), так и перед/после получения запроса (OnRequest Script/AfterRequest Script).
Начнем:
1) В SoapUI создать новый проект, например, "test-soapui-project" (либо выбрать существующий) и создать для него два REST MockService.
![](https://habrastorage.org/getpro/habr/upload_files/4b0/cbb/bdc/4b0cbbbdce384fcbdda0974786ef32b6.png)
Первый назовем Validation REST MockService - имитирует сервис внешней системы:
в Path произвольный путь к моксервису, например, "/SP/ValidationSystem",
в Port значение порта на котором будет запущен мок,
в Host значение "localhost".
![](https://habrastorage.org/getpro/habr/upload_files/cb9/10c/0f8/cb910c0f8727ee52db581680e1ec2e2f.png)
Второй назовем Validation Receiving CALLBACK REST MockService - имитирует сервис принимающий callback (данный мок нужен для локальной проверки "Validation REST MockService" перед тем как мы его запустим на общем сервере):
Аналогично в Path произвольный путь к моксервису, например, "/SP/SpHost"
![](https://habrastorage.org/getpro/habr/upload_files/990/cf6/ff7/990cf6ff7cd74ef8645c58522fd2fd67.png)
2) В мок Validation REST MockService добавить:
запрос PUT
![](https://habrastorage.org/getpro/habr/upload_files/79a/560/15f/79a56015fae81c1397867db3a3351054.png)
Указываем:
в Resource Path произвольный путь к методу внутри моксервиса, например, "/validation"
в Method указываем метод Rest запроса, в нашем случае PUT (шаг 1 в задаче)
![](https://habrastorage.org/getpro/habr/upload_files/a92/4fb/24e/a924fb24efa8fdf1d1a9226d7534ef60.png)
для запроса PUT добавить ответ 204 No content
![](https://habrastorage.org/getpro/habr/upload_files/d7a/857/893/d7a857893170ea65cb80d77777b50d6d.png)
Указываем:
в Http Status Code код ответа на запрос, в нашем случае "204 - No Content",
в Content | Media type тип ответа на запроса, в нашем случае "application/json".
![](https://habrastorage.org/getpro/habr/upload_files/f85/591/93f/f8559193f225b6510bf3316a40808eaf.png)
переменные в Properties:
![](https://habrastorage.org/getpro/habr/upload_files/372/2c5/028/3722c5028c1acbfd32a1fb18d0376e38.png)
это переменные в которые будут сохранены поля promotionalCode, replyTo, correlationId из поступившего запроса PUT.
OnRequest Script - данный скрипт будет выполняться перед отправкой синхронного ответа 204 No content (шаг 2 задачи). Получаем из запроса необходимые значения и записываем в Properties:
![](https://habrastorage.org/getpro/habr/upload_files/af8/7f6/99a/af87f699aa993672904c2285c6a6b484.png)
*log.info можно не использовать, носит информативный характер для проверки хода выполнения операций на вкладке script log.
AfterRequest Script - данный скрипт выполнится после отправки синхронного ответа. Тут наша задача сформировать callback на основании полученных данных, имитировать небольшую задержку (в нашем случае 5 секунд), отправить callback по адресу replyTo (шаг 3 задачи):
![](https://habrastorage.org/getpro/habr/upload_files/b63/e0d/b14/b63e0db14eec440d135e0a6d94809b8a.png)
Сам скрипт:
log.info" Afterequest Script Синхрона - начало"
import java.lang.Exception
//Создаем переменные для формирования callback'а , используя значения из Properties
def replyTo = context.mockService.getPropertyValue('replyTo')
def correlationId = context.mockService.getPropertyValue('correlationId')
def promotionalCode = context.mockService.getPropertyValue('promotionalCode')
def status = "testStatus"
if ( promotionalCode.contains("correctPromotionalCode")){
status="success"
}
else
{
status="validationError"
}
//Произвольное ожидание
Thread.sleep(5000)
log.info"до Отправки callback"
// формируем callback - Запрос POST
def conn = new URL(replyTo).openConnection();
def message = """{
"status":"${status}",
"correlationId":"${correlationId}"
}
"""
conn.setRequestMethod("POST")
conn.setDoOutput(true)
conn.setRequestProperty("Content-Type", "application/json")
conn.getOutputStream().write(message.getBytes("UTF-8"));
def postRC = conn.getResponseCode();
println(postRC);
if(postRC.equals(200)) {
println(conn.getInputStream().getText());
}
log.info"после Отправки callback"
log.info" Afterequest Script Синхрона - конец"
3) В мок Validation Receiving CALLBACK REST MockService добавить:
запрос POST
Указываем:
в Resource Path произвольный путь к методу внутри моксервиса, например, "/callback"
в Method указываем метод Rest запроса, в нашем случае POST.
![](https://habrastorage.org/getpro/habr/upload_files/0c9/8e2/e33/0c98e2e33d44bd7a84f5584cee3e6d53.png)
для запроса POST добавить ответ 200 OK
Указываем:
в Http Status Code код ответа на запрос, в нашем случае "200 - OK",
в Content | Media type тип ответа на запроса, в нашем случае "application/json".
![](https://habrastorage.org/getpro/habr/upload_files/6d4/c12/66d/6d4c1266d664dede9ff1608ac0d0a511.png)
OnRequest Script - в скрипте мы выводим сообщение, что callback успешно получен и смотрим, что в нем пришло:
![](https://habrastorage.org/getpro/habr/upload_files/161/ab1/663/161ab1663425fec548425cffc60d2d5d.png)
4) Запустить оба моксервиса
![](https://habrastorage.org/getpro/habr/upload_files/8f2/1b6/541/8f21b6541991ecbb8fb693f2d2c07399.png)
5) Проверяем работу моксервисов
Отправляем запрос проверки валидации, например, через Postman:
Выполняем PUT вызов метода validation на Мок 1 (Validation REST MockService), указывая в Headers 3 параметра, как описано в Задаче
В replyTo указываем адрес сервиса принимающего callback - Мок 2 (Validation Receiving CALLBACK REST MockService)
![](https://habrastorage.org/getpro/habr/upload_files/eb7/60e/4b0/eb760e4b09b113fb65a256f41876d3e3.png)
При выполнении запроса видим в ответе - 204 No Content.
Проверяем в script log SoapUI , что callback пришел на Мок 2:
![](https://habrastorage.org/getpro/habr/upload_files/7b2/ac5/21d/7b2ac521dbde4ec334d78fe360d152c8.png)
Запуск моксервиса в Portainer:
1) В images добавляем образ для работы с моксервисами SoapUI:
находим на docker hub необходимый образ. в нашем случае fbascheper/soapui-mockservice-runner (https://hub.docker.com/r/fbascheper/soapui-mockservice-runner/)<o:p>
переходим в Portainer в раздел images
по названию загружаем необходимый образ
![](https://habrastorage.org/getpro/habr/upload_files/9be/481/337/9be481337b6bda95c481849798f1dfa5.png)
2) Разворачиваем стек для работы с моксервисами :
Составить Compose file:
version: "3.7"
services:
soapui-validation:
image: fbascheper/soapui-mockservice-runner:latest
volumes:
- /soapui/soapui-prj:/home/soapui/soapui-prj
environment:
- MOCK_SERVICE_NAME=Validation REST MockService
- PROJECT=/home/soapui/soapui-prj/test-soapui-project.xml
ports:
- 7703:8080
где:
image - образ загруженный с docker hub (https://hub.docker.com/r/fbascheper/soapui-mockservice-runner/)
volumes - указать где на сервере расположен проект soapUI и куда его необходимо расположить в контейнере, формат: "директория на сервере": "директория в контейнере"
MOCK_SERVICE_NAME - название моксервиса внутри проекта soapUI
PROJECT - путь к проекту soapUI в контейнере, формат: «значение "директория в контейнере" из volumes» + само название проекта(в нашем случае test-soapui-project)
ports - указать внешний порт по которому моксервис будет доступен извне и на каком порте запущен в контейнере (значение port из soapUI), формат "внешний порт": "порт в контейнере"
*в нашем примере проект soapUI разместили на сервере в /soapui/soapui-prj/
перейти в Portainer в раздел Stacks,
добавить новый стек:
- указать в поле Name, как будет назван наш стек, например, sp-soapui-mocks,
- в Web editor копируем текст Compose file,
Deploy the stack.
В результате будет сформирован стек sp-soapui-mocks, в котором запущен сервис , указанный в compose file:
![](https://habrastorage.org/getpro/habr/upload_files/7cd/4e5/19d/7cd4e519d02c25b129d50c4ae7e78b3d.png)
Итого
Мы получили асинхронный моксервис , который удовлетворяет поставленной задаче. Сервис запущен на общем сервере и доступен всем участникам проекта. В данный момент он помогает нам разрабатывать и тестировать приложение при отсутствии API сторонней системы.