{"id":13644921,"url":"https://github.com/lamoda/gonkey","last_synced_at":"2025-05-15T13:07:13.457Z","repository":{"id":37412442,"uuid":"191345885","full_name":"lamoda/gonkey","owner":"lamoda","description":"Gonkey - a testing automation tool","archived":false,"fork":false,"pushed_at":"2025-04-22T15:26:49.000Z","size":545,"stargazers_count":373,"open_issues_count":38,"forks_count":54,"subscribers_count":20,"default_branch":"master","last_synced_at":"2025-04-22T16:57:28.721Z","etag":null,"topics":["autotesting","fixtures","microservices","mocks","testing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lamoda.png","metadata":{"files":{"readme":"README-ru.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-06-11T10:09:41.000Z","updated_at":"2025-04-21T13:15:04.000Z","dependencies_parsed_at":"2023-11-20T10:37:36.660Z","dependency_job_id":"40a3f3c6-b5cc-4ed9-8eb7-5e2699c32539","html_url":"https://github.com/lamoda/gonkey","commit_stats":null,"previous_names":[],"tags_count":56,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lamoda%2Fgonkey","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lamoda%2Fgonkey/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lamoda%2Fgonkey/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lamoda%2Fgonkey/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lamoda","download_url":"https://codeload.github.com/lamoda/gonkey/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254346624,"owners_count":22055808,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["autotesting","fixtures","microservices","mocks","testing"],"created_at":"2024-08-02T01:02:19.489Z","updated_at":"2025-05-15T13:07:08.433Z","avatar_url":"https://github.com/lamoda.png","language":"Go","funding_links":[],"categories":["Go","Go (531)"],"sub_categories":[],"readme":"# Gonkey: инструмент автоматизации тестирования\n\nGonkey протестирует ваши сервисы, используя их API. Он умеет обстреливать сервис заранее заготовленными запросами и проверять ответы. Сценарий теста описывается в YAML-файле.\n\nВозможности:\n\n- работает с REST/JSON API\n- проверка API сервиса на соответствие OpenAPI-спеке\n- заполнение БД сервиса данными из фикстур (поддерживается PostgreSQL, MySQL, Aerospike, Redis)\n- моки для имитации внешних сервисов\n- можно подключить к проекту как библиотеку и запускать вместе с юнит-тестами\n- запись результата тестов в виде отчета [Allure](http://allure.qatools.ru/)\n- имеется [JSON-schema](#json-schema) для автодополнения и валидации YAML-файлов Gonkey\n\n## Содержание\n\n- [Gonkey: инструмент автоматизации тестирования](#gonkey-инструмент-автоматизации-тестирования)\n  - [Содержание](#содержание)\n  - [Использование консольной утилиты](#использование-консольной-утилиты)\n  - [Использование gonkey как библиотеки](#использование-gonkey-как-библиотеки)\n  - [Пример тестового сценария](#пример-тестового-сценария)\n  - [Статус теста](#статус-теста)\n  - [HTTP-запрос](#http-запрос)\n  - [HTTP-ответ](#http-ответ)\n  - [Переменные](#переменные)\n    - [Способы присвоения](#способы-присвоения)\n      - [В описании самого теста](#в-описании-самого-теста)\n      - [Из результатов предыдущего запроса](#из-результатов-предыдущего-запроса)\n      - [Из результата текущего запроса](#из-результата-текущего-запроса)\n      - [В переменных окружения или в env-файле](#в-переменных-окружения-или-в-env-файле)\n      - [В cases](#в-cases)\n  - [Запросы с multipart/form-data](#запросы-с-multipartform-data)\n    - [Данные полей формы](#данные-полей-формы)\n    - [Загрузка файлов](#загрузка-файлов)\n  - [Фикстуры](#фикстуры)\n    - [Удаление данных из таблиц](#удаление-данных-из-таблиц)\n    - [Шаблоны записей](#шаблоны-записей)\n    - [Наследование записей](#наследование-записей)\n    - [Связывание записей](#связывание-записей)\n    - [Выражения](#выражения)\n    - [Aerospike](#aerospike)\n    - [Redis](#redis)\n  - [Моки](#моки)\n    - [Запуск моков при использовании gonkey как библиотеки](#запуск-моков-при-использовании-gonkey-как-библиотеки)\n    - [Описание моков в файле с тестом](#описание-моков-в-файле-с-тестом)\n      - [Проверки запросов (requestConstraints)](#проверки-запросов-requestconstraints)\n        - [nop](#nop)\n        - [bodyMatchesJSON](#bodymatchesjson)\n        - [bodyJSONFieldMatchesJSON](#bodyjsonfieldmatchesjson)\n        - [pathMatches](#pathmatches)\n        - [queryMatches](#querymatches)\n        - [queryMatchesRegexp](#querymatchesregexp)\n        - [methodIs](#methodis)\n        - [headerIs](#headeris)\n        - [bodyMatchesText](#bodymatchestext)\n        - [bodyMatchesXML](#bodymatchesxml)\n      - [Стратегии ответов (strategy)](#стратегии-ответов-strategy)\n        - [nop](#nop-1)\n        - [file](#file)\n        - [constant](#constant)\n        - [template](#template)\n        - [uriVary](#urivary)\n        - [methodVary](#methodvary)\n        - [sequence](#sequence)\n        - [basedOnRequest](#basedonrequest)\n        - [dropRequest](#droprequest)\n      - [Подсчет количества вызовов](#подсчет-количества-вызовов)\n  - [Использование shell скриптов](#использование-shell-скриптов)\n    - [Описание скрипта](#описание-скрипта)\n    - [Запуск скрипта с параметризацией](#запуск-скрипта-с-параметризацией)\n  - [Запрос в Базу данных](#запрос-в-базу-данных)\n    - [Формат описания запросов](#формат-описания-запросов)\n    - [Описание запроса](#описание-запроса)\n    - [Описание ответа на запрос в Базу данных](#описание-ответа-на-запрос-в-базу-данных)\n    - [Параметризация при запросах в Базу данных](#параметризация-при-запросах-в-базу-данных)\n    - [Игнорирование порядка записей в ответе на запрос в базу данных](#игнорирование-порядка-записей-в-ответе-на-запрос-в-базу-данных)\n  - [JSON-schema](#json-schema)\n    - [Настройка на IDE Jetbrains](#настройка-на-ide-jetbrains)\n    - [Настройка на IDE VSCode](#настройка-на-ide-vscode)\n\n## Использование консольной утилиты\n\nДля тестирование сервиса, размещенного на удаленном хосте, используйте gonkey как консольную утилиту.\n\n`./gonkey -host \u003c...\u003e -tests \u003c...\u003e [-spec \u003c...\u003e] [-db_dsn \u003c...\u003e -fixtures \u003c...\u003e] [-allure] [-v]`\n\n- `-spec \u003c...\u003e` путь к файлу или URL со swagger-спецификацией сервиса\n- `-host \u003c...\u003e` хост:порт сервиса\n- `-tests \u003c...\u003e` файл или директория с тестами\n- `-db-type \u003c...\u003e` - тип базы данных. В данный момент поддерживается PostgreSQL, Aerospike, Redis.\n- `-db_dsn \u003c...\u003e` dsn для вашей тестовой SQL базы данных (бд будет очищена перед наполнением!), поддерживается только PostgreSQL\n- `-aerospike_host \u003c...\u003e` при использовании Aerospike - URL для подключения к нему в формате `host:port/namespace`\n- `-redis_url \u003c...\u003e` при использовании Redis - адрес для подключения к Redis, например `redis://user:password@localhost:6789/1?dial_timeout=1\u0026db=1\u0026read_timeout=6s\u0026max_retries=2`\n- `-fixtures \u003c...\u003e` директория с вашими фикстурами\n- `-allure` генерировать allure-отчет\n- `-v` подробный вывод\n- `-debug` отладочный вывод\n\nВ таком режиме моки использовать не получится.\n\n## Использование gonkey как библиотеки\n\nЧтобы интегрировать функциональные тесты в нативные тесты Go и запускать их вместе, используйте gonkey как библиотеку.\n\nСоздайте файл для будущего теста, например, `func_test.go`.\n\nПодключите gonkey как зависимость к вашему проекту в этом файле.\n\n```go\nimport (\n    \"github.com/lamoda/gonkey/runner\"\n    \"github.com/lamoda/gonkey/mocks\"\n)\n```\n\nСоздайте функцию с тестом.\n\n```go\npackage test\n\nimport (\n  \"testing\"\n\n  \"github.com/lamoda/gonkey/fixtures\"\n  \"github.com/lamoda/gonkey/mocks\"\n  \"github.com/lamoda/gonkey/runner\"\n)\n\nfunc TestFuncCases(t *testing.T) {\n  // проинициализируйте моки, если нужно (подробнее - ниже)\n  // m := mocks.NewNop(...)\n\n  // проинициализируйте базу для загрузки фикстур, если нужно (подробнее - ниже)\n  // db := ...\n\n  // проинициализируйте Aerospike для загрузки фикстур, если нужно (подробнее - ниже)\n  // aerospikeClient := ...\n\n  // создайте экземпляр сервера вашего приложения\n  srv := server.NewServer()\n  defer srv.Close()\n\n  // запустите выполнение тестов из директории cases с записью в отчет Allure\n  runner.RunWithTesting(t, \u0026runner.RunWithTestingParams{\n    Server:   srv,\n    TestsDir: \"cases\",\n    Mocks:    m,\n    DB:       db,\n    Aerospike: runner.Aerospike{\n      Client:    aerospikeClient,\n      Namespace: \"test\",\n    },\n    // Тип используемой базы данных, возможные значения fixtures.Postgres, fixtures.Mysql, fixtures.Aerospike, fixtures.CustomLoader\n    // Если в параметр DB не пустой, а данный параметр не назначен, будет использоваться тип бд fixtures.Postgresql\n    DbType:      fixtures.Postgres,\n    FixturesDir: \"fixtures\",\n  })\n}\n```\n\nНачиная с версии 1.18.3, добавлена поддержка внешних модулей для загрузки тестовых данных из фикстур, если gonkey используется как библиотека.\nЧтобы начать использовать внешний загрузчик, вы должны импортировать модуль, содержащий реализацию интерфейса fixtures.Loader.\n\nПример для загрузки данных в Redis\n\n```go\npackage test\n\nimport (\n  \"net/http\"\n  \"net/http/httptest\"\n  \"testing\"\n\n  \"github.com/lamoda/gonkey/fixtures\"\n  redisLoader \"github.com/lamoda/gonkey/fixtures/redis\"\n  // redisLoader \"custom_module/gonkey-redis\" // внешняя библиотека, содержащая реализацию интерфейса fixtures.Loader\n  redisClient \"github.com/redis/go-redis/v9\"\n  \"github.com/lamoda/gonkey/runner\"\n)\n\nfunc TestFuncCases(t *testing.T) {\n  serveMux := http.NewServeMux()\n  \n  serveMux.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n    _, _ = w.Write([]byte(\"ok\"))\n  })\n  \n  srv := httptest.NewServer(serveMux)\n  \n  clientOptions, err := redisClient.ParseURL(\"redis://user:password@localhost:6789/1?dial_timeout=1\u0026db=1\u0026read_timeout=6s\u0026max_retries=2\")\n  if err != nil {\n      panic(err)\n  }\n\n  redisFixtureLoader := redisLoader.New(redisLoader.LoaderOptions{\n    FixtureDir: \"./fixtures\",\n    Redis:      clientOptions,\n  })\n\n  runner.RunWithTesting(t, \u0026runner.RunWithTestingParams{\n    Server:        srv,\n    TestsDir:      \"./cases\",\n    DbType:        fixtures.CustomLoader,\n    FixtureLoader: redisFixtureLoader,\n  })\n}\n```\n\nТеперь тесты можно запускать через `go test`, например, так: `go test ./...`.\n\n## Пример тестового сценария\n\n```yaml\n- name: КОГДА запрашивается список заказов ДОЛЖЕН успешно возвращаться\n  method: GET\n  status: \"\"\n  path: /jsonrpc/v2/order.getBriefList\n  query: ?id=550e8400-e29b-41d4-a716-446655440000\u0026jsonrpc=2.0\u0026user_id=00001\n\n  fixtures:\n    - order_0001\n    - order_0002\n\n  response:\n    200: |\n      {\n        \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n        \"jsonrpc\": \"2.0\",\n        \"result\": {\n          \"data\": [\n            \"ORDER0001\",\n            \"ORDER0002\"\n          ],\n          \"meta\": {\n            \"items\": 0,\n            \"limit\": 50,\n            \"page\": 0,\n            \"pages\": 0\n          }\n        }\n      }\n\n- name: КОГДА запрашивается один заказ ДОЛЖЕН возвращаться пользователь и сумма\n  method: POST\n  path: /jsonrpc/v2/order.getOrder\n\n  headers:\n    Authorization: Bearer HsHG67d38hJKJFdfjj==\n    Content-Type: application/json\n\n  cookies:\n    sid: ZmEwZDkwYzgwMmQzMGIzOGIxODM3ZmFiOTGJhMzU=\n    lid: AAAEAFu/TdhHBg7UAgA=\n\n  comparisonParams:\n    ignoreValues: false\n    ignoreArraysOrdering: false\n    disallowExtraFields: false\n\n  request: |\n    {\n      \"jsonrpc\": \"2.0\",\n      \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n      \"method\": \"order.getOrder\",\n      \"params\": [\n        {\n          \"order_nr\": {{ .orderNr }}\n        }\n      ]\n    }\n\n  response:\n    200: |\n      {\n        \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n        \"jsonrpc\": \"2.0\",\n        \"result\": {\n          \"user_id\": {{ .userId }},\n          \"amount\": {{ .amount }}\n        }\n      }\n\nresponseHeaders:\n    200:\n      Content-Type: \"application/json\"\n      Cache-Control: \"no-store, must-revalidate\"\n      Set-Cookie: \"mycookie=123; Path=/; Domain=mydomain.com\", \"mycookie=456; Path=/; Domain=.mydomain.com\"\n\n  cases:\n    - requestArgs:\n        orderNr: ORDER0001\n      responseArgs:\n        200:\n          userId: '0001'\n          amount: 1000\n\n    - requestArgs:\n        orderNr: ORDER0002\n      responseArgs:\n        200:\n          userId: '0001'\n          amount: 72000\n```\n\nКак видно из примера, вы можете использовать Regexp для проверки тела ответа.\nОни могут быть использованы для проверки всего тела (если это просто текст):\n\n```yaml\n    response:\n        200: \"$matchRegexp(^xy+z$)\"\n```\n\nили для элементов map/array (если это JSON):\n\n```yaml\n    response:\n        200: |\n          {\n            \"id\": \"$matchRegexp([\\\\w-]+)\",\n            \"jsonrpc\": \"$matchRegexp([12].0)\",\n            \"result\": [\n              \"data\": [\n                  \"$matchRegexp(ORDER[0]{3}[0-9])\",\n                  \"$matchRegexp(ORDER[0]{3}[0-9])\"\n              ],\n            ]\n          }\n```\n\nТак же в поле query вначале указывать \"?\" необязательно\n\n## Статус теста\n\n`status` - параметр, для того чтобы помечать тесты, может иметь следующие значения:\n\n- `broken` - такой тест не будет запущен, в отчете будет отмечен как `broken`\n- `skipped` - такой тест не будет запущен, в отчете будет отмечен как `skipped`\n- `focus` - если у теста выставлен такой статус, все остальные тесты в suite у которых не проставлен статус, будут отмечены как `skipped` и будут запущены только тесты с статусом `focus`\n\n## HTTP-запрос\n\n`method` - параметр для передачи типа HTTP запроса, формат передачи указан в примере выше\n\n`path` - параметр для передачи URL-пути, формат передачи указан в примере выше\n\n`headers` - параметр для передачи http-заголовков, формат передачи указан в примере выше.\n\n`cookies` -  параметр для передачи cookie, формат передачи указан в примере выше.\n\n## HTTP-ответ\n\n`response` - тело ответа HTTP для указанных кодов состояния HTTP.\n\n`responseHeaders` - все заголовки ответа HTTP для указанных кодов состояния HTTP.\n\n## Переменные\n\nВ описании теста можно использовать переменные, они поддерживаются в следующих полях:\n\n- method\n- description\n- path\n- query\n- headers\n- request\n- response\n- dbQuery\n- dbResponse\n- body для моков\n- headers для моков\n- requestConstraints для моков\n- form для multipart/form-data\n\nПример использования:\n\n```yaml\n- method: \"{{ $method }}\"\n  description: \"{{ $description }}\"\n  path: \"/some/path/{{ $pathPart }}\"\n  query: \"{{ $query }}\"\n  headers:\n    header1: \"{{ $header }}\"\n  request: '{\"reqParam\": \"{{ $reqParam }}\"}'\n  response:\n    200: \"{{ $resp }}\"\n  mocks:\n    server_mock:\n      strategy: constant\n      body: \u003e\n        {\n          \"message\": \"{{ $mockParam }}\"\n        }\n      statusCode: 200\n  dbQuery: \u003e\n    SELECT id, name FROM testing_tools WHERE id={{ $sqlQueryParam }}\n  dbResponse:\n    - '{\"id\": {{ $sqlResultParam }}, \"name\": \"gonkey\"}'\n```\n\nПрисваивать значения переменным можно следующими способами:\n\n- в описании самого теста\n- из результатов предыдущего запроса\n- из результата текущего запроса\n- в переменных окружения или в env-файле\n\nПриоритеты источников соответствуют порядку перечисления.\n\n### Способы присвоения\n\n#### В описании самого теста\n\nПример:\n\n```yaml\n- method: \"{{ $method }}\"\n  path: \"/some/path/{{ $pathPart }}\"\n  variables:\n    reqParam: \"reqParam_value\"\n    method: \"POST\"\n    pathPart: \"part_of_path\"\n    query: \"query_val\"\n    header: \"header_val\"\n    resp: \"resp_val\"\n  query: \"{{ $query }}\"\n  headers:\n    header1: \"{{ $header }}\"\n  request: '{\"reqParam\": \"{{ $reqParam }}\"}'\n  response:\n    200: \"{{ $resp }}\"\n```\n\n#### Из результатов предыдущего запроса\n\nПример:\n\n```yaml\n# если в ответе plain text\n- name: \"get_last_post_id\"\n  ...\n  variables_to_set:\n          200: \"id\"\n\n# если в ответе JSON\n- name: \"get_last_post_info\"\n  variables_to_set:\n          200:\n            id: \"id\"\n            title: \"title\"\n            authorId: \"author_info.id\"\n```\n\nОбратите внимание - если нужно использовать значение вложенного поля, можно указать путь до него:\n\u003e \"author_info.id\"\n\nГлубина вложенности может быть любая.\n\n#### Из результата текущего запроса\n\nПример:\n\n```yaml\n- name: Get info with database\n  method: GET\n  path: \"/info/1\"\n  variables_to_set:\n    200:\n      golang_id: query_result.0.0\n  response:\n    200: '{\"result_id\": \"1\", \"query_result\": [[ {{ $golang_id }} , \"golang\"], [2, \"gonkey\"]]}'\n  dbQuery: \u003e\n    SELECT id, name FROM testing_tools WHERE id={{ $golang_id }}\n  dbResponse:\n    - '{\"id\": {{ $golang_id}}, \"name\": \"golang\"}'\n```\n\n#### В переменных окружения или в env-файле\n\nGonkey автоматически проверяет наличие указанной переменной среди переменных окружения (в таком же регистре) и берет значение оттуда, в случае наличия.\n\nЕсли указан env-файл, то описанные в нем переменные добавятся/заменят соответствующие перемнные окружения.\nenv-файл указывается с помощью параметра env-file\n\nПример env-файла (стандартный синтаксис):\n\n```.env\njwt=some_jwt_value\nsecret=my_secret\npassword=private_password\n```\n\nenv-файл, например, удобно использовать, когда нужно вынести из теста приватную информацию (пароли, ключи и т.п.)\n\n#### В cases\n\nПеременные могут быть заданы в блоке *cases*.\n\nПример:\n\n```yaml\n- name: Get user info\n  method: GET\n  path: \"/user/1\"\n  response:\n    200: '{ \"user_id\": \"1\", \"name\": \"{{ $name }}\", \"surname\": \"{{ $surname }}\" }'\n  cases:\n    - variables:\n        name: John\n        surname: Doe\n```\n\nТакие переменные будут доступны и в других кейсах, если не будут переопределены.\n\n## Запросы с multipart/form-data\nНужно указать тип запроса\n- POST\n\nЗаголовок (необязательно):\n\u003e Content-Type: multipart/form-data\n\nили с указанием _boundary_ (необязательно):\n\u003e Content-Type: multipart/form-data; boundary=--some-boundary\n\n### Данные полей формы\nПример:\n\n```yaml\n - name: \"upload-form\"\n   method: POST\n   form:\n     fields:\n       field_name1: \"field_name1 value\"\n       name2: \"name2 value\"\n       \"custom_struct_field[0]\": \"custom_struct_field 0\"\n       \"custom_struct_field[1]\": \"custom_struct_field 1\"\n       \"custom_struct_field[inner_obj][field]\": \"inner_obj field value\"\n   headers:\n     Content-Type: multipart/form-data # case-sensitive, can be omitted\n   response:\n     200: |\n       {\n         \"status\": \"OK\"\n       }\n```\n\n### Загрузка файлов\n*по пути с файловой системы\n\nПример:\n\n```yaml\n - name: \"upload-files\"\n   method: POST\n   form:\n       files:\n         file1: \"testdata/upload-files/file1.txt\"\n         file2: \"testdata/upload-files/file2.log\"\n   headers:\n     Content-Type: multipart/form-data\n   response:\n     200: |\n       {\n         \"status\": \"OK\"\n       }\n```\n\nс данными формы:\n```yaml\n - name: \"upload-multipart-form-data\"\n   method: POST\n   form:\n     fields:\n       field_name1: \"field_name1 value\"\n     files:\n       file1: \"testdata/upload-files/file1.txt\"\n       file2: \"testdata/upload-files/file2.log\"\n   headers:\n     Content-Type: multipart/form-data # case-sensitive, can be omitted\n   response:\n     200: |\n       {\n         \"status\": \"OK\"\n       }\n```\n\n## Фикстуры\n\nЧтобы наполнить базу перед тестом, используются файлы с фикстурами.\n\nПример файла:\n\n```yaml\n# fixtures/comments.yml\ninherits:\n  - another_fixture\n  - yet_another_fixture\n\ntables:\n  posts:\n    - $name: janes_post\n      title: New post\n      text: Post text\n      author: Jane Dow\n      created_at: 2016-01-01 12:30:12\n      updated_at: 2016-01-01 12:30:12\n\n    - $name: apples_post\n      title: Morning digest\n      text: Text\n      author: Apple Seed\n      created_at: 2016-01-01 12:30:12\n      updated_at: 2016-01-01 12:30:12\n\n  comments:\n    - post_id: $janes_post.id\n      content: A comment...\n      author_name: John Doe\n      author_email: john@doe.com\n      created_at: 2016-01-01 12:30:12\n      updated_at: 2016-01-01 12:30:12\n\n    - post_id: $apples_post.id\n      content: Another comment...\n      author_name: John Doe\n      author_email: john@doe.com\n      created_at: 2016-01-01 12:30:12\n      updated_at: 2016-01-01 12:30:12\n\n  another_table:\n      ...\n  ...\n```\n\nЗаписи в фикстурах можно наследовать одну от другой, использовать шаблоны, а так же ссылаться из одной записи на другую.\n\n### Удаление данных из таблиц\n\nЧтобы очистить таблицу перед тестом, укажите пару квадратных скобок рядом с названием таблицы в файле с фикстурами.\n\nПример:\n\n```yaml\n# fixtures/empty_posts_table.yml\ntables:\n  posts: []\n```\n\n### Шаблоны записей\n\nОбычно, чтобы вставить строку данных в базу, вам нужно перечислить все поля, для которых в базе не предусмотрено значение по умолчанию. Довольно часто, многие из этих полей не важны для теста и их значения повторяются от одной фикстуры к другой, создавая ненужный визуальный мусор и усложняя их поддержку.\n\nС помощью шаблонов вы можете наследовать поля из шаблонной записи, каждый раз переопределяя только те поля, которые действительно важны для теста.\n\nПример определения шаблона:\n\n```yaml\ntemplates:\n  dummy_client:\n    name: Dummy Client Name\n    age: 35\n    ip: 127.0.0.1\n    is_deleted: false\n\n  dummy_deleted_client:\n    $extend: dummy_client\n    is_deleted: true\n\ntables:\n  ...\n```\n\nПример использования шаблона в фикстуре:\n\n```yaml\ntemplates:\n   ...\ntables:\n   clients:\n      - $extend: dummy_client\n      - $extend: dummy_client\n        name: Josh\n      - $extend: dummy_deleted_client\n        name: Jane\n```\n\nКак вы могли заметить, шаблоны тоже можно наследовать друг от друга с помощью ключевого слова `$extend`, но только если к моменту определения зависимого шаблона базовый шаблон уже определен (в этом же файле или любом, подключенном через `inherits`).\n\n### Наследование записей\n\nЗаписи, как и шаблоны, можно наследовать с помощью `$extend`.\n\nДля того, чтобы унаследовать запись, сначала нужно присвоить строке имя с помощью `$name`:\n\n```yaml\n# fixtures/post.yaml\ntables:\n  posts:\n    - $name: regular_post\n      title: Post title\n      text: Some text\n```\n\nИмена, присваеваемые строкам, должны быть уникальны среди всех загружаемых файлов с фикстурами, а также не пересекаться с именами шаблонов.\n\nВ другом файле фикстур нужно объявить, что определенная строка наследует ранее объявленную запись с помощью `$extend`, так же, как и в случае с шаблоном:\n\n```yaml\n# fixtures/deleted_post.yaml\ninherits:\n  - post\ntables:\n  posts:\n    - $extend: regular_post\n      is_deleted: true\n```\n\nНе забудьте объявить зависимость между файлами в `inherits`, чтобы один файл всегда загружался вместе с другим.\n\nОбратите внимание, что наследование строк работает только между разными файлами фикстур. В пределах одного файла объявить наследование невозможно.\n\n### Связывание записей\n\nНесмотря на то, что файлы фикстур позволяют вам задавать значение для автоинкрементных колонок (обычно `id`), не рекомендуется этого делать. Контролировать, чтобы в разных файлах использовались правильные значения для `id`, и чтобы они нигде не пересеклись, очень сложно. Чтобы база сама присвоила значение автоинкрементному полю, достаточно просто не указывать его значение явно.\n\nОднако, если не указывать значение для `id`, то как связать несколько сущностей, которые должны ссылаться друг на друга по идентификаторам? Фикстуры позволяют ссылаться на значение ранее вставленных в базу строк по их имени, используя нотацию `$refName.fieldName`.\n\nОбъявим именованную запись:\n\n```yaml\n# fixtures/post.yaml\ntables:\n  posts:\n    - $name: regular_post\n      title: Post title\n      text: Some text\n```\n\nТеперь, чтобы связать таблицы `posts` и `comments`, обратимся к записи по имени (`$regular_post`) и укажем поле, из которого следует взять значение (`id`):\n\n```yaml\n# fixtures/comment.yaml\ntables:\n  comments:\n    - post_id: $regular_post.id\n      content: A comment...\n      author_name: John Doe\n```\n\nСсылаться можно только на поля ранее вставленной в базу записи, на поля шаблона ссылаться нельзя, при попытке это сделать, вы получите ошибку `undefined reference`.\n\nОбратите внимание на ограничение: нельзя ссылаться на записи в пределах одной таблицы одного файла.\n\n### Выражения\n\nЕсли в базу нужно записать не статичное значение, а результат исполнения выражения, то можно воспользоваться конструкцией `$eval()`. Все, что будет задано внутри скобок, будет вставлено в базу в сыром, неэкранированном виде. Таким образом, внутри `$eval()` можно написать все то, что вы могли бы написать в самом запросе.\n\nНапример, такая конструкция вставит текущую дату и время в качестве значения поля:\n\n```yaml\ntables:\n  comments:\n    - created_at: $eval(NOW())\n```\n\n### Aerospike\n\nДля хранилища Aerospike также поддерживается заливка тестовых данных. Для этого важно не забыть при запуске gonkey как CLI-приложение использовать флаг `-db-type aerospike`, а при использовании в качестве библиотеки в конфигурации раннера: `DbType: fixtures.Aerospike`.\n\nФормат файлов с фикстурами для Aerospike отличается, но смысл остаётся прежним:\n\n```yaml\nsets:\n  set1:\n    key1:\n      bin1: \"value1\"\n      bin2: 1\n    key2:\n      bin1: \"value2\"\n      bin2: 2\n      bin3: 2.569947773654566473\n  set2:\n    key1:\n      bin4: false\n      bin5: null\n      bin1: '\"'\n    key2:\n      bin1: \"'\"\n      bin5:\n        - 1\n        - '2'\n```\n\nТакже поддерживаются шаблоны:\n\n```yaml\ntemplates:\n  base_tmpl:\n    bin1: value1\n  extended_tmpl:\n    $extend: base_tmpl\n    bin2: value2\n\nsets:\n  set1:\n    key1:\n      $extend: base_tmpl\n      bin1: overwritten\n  set2:\n    key1:\n      $extend: extended_tmpl\n      bin2: overwritten\n```\n\nСвязывание записей и выражения на данный момент не поддерживаются.\n\n### Redis\n\nПоддерживается загрузка тестовых данных через фикстуры для хранилища ключ/значение Redis\n\nСписок, поддерживаемых структур данных:\n\n- Пара ключ/значение\n- Set\n- Hash\n- List\n- ZSet (sorted set)\n\nСвязывание записей и выражения на данный момент не поддерживаются.\n\nПример файла фикстуры:\n\n```yaml\ninherits:\n  - template1\n  - template2\n  - other_fixture\ntemplates:\n  keys:\n    - $name: parentKeyTemplate\n      values:\n        baseKey:\n          expiration: 1s\n          value: 1\n    - $name: childKeyTemplate\n      $extend: parentKeyTemplate\n      values:\n        otherKey:\n          value: 2\n  sets:\n    - $name: parentSetTemplate\n      expiration: 10s\n      values:\n        - value: a\n    - $name: childSetTemplate\n      $extend: parentSetTemplate\n      values:\n        - value: b\n  hashes:\n    - $name: parentHashTemplate\n      values:\n        - key: a\n          value: 1\n        - key: b\n          value: 2\n    - $name: childHashTemplate\n      $extend: parentHashTemplate\n      values:\n        - key: c\n          value: 3\n        - key: d\n          value: 4\n  lists:\n    - $name: parentListTemplate\n      values:\n        - value: 1\n        - value: 2\n    - $name: childListTemplate\n      values:\n        - value: 3\n        - value: 4\n  zsets:\n    - $name: parentZSetTemplate\n      values:\n        - value: 1\n          score: 2.1\n        - value: 2\n          score: 4.3\n    - $name: childZSetTemplate\n      value:\n        - value: 3\n          score: 6.5\n        - value: 4\n          score: 8.7\ndatabases:\n  1:\n    keys:\n      $extend: childKeyTemplate\n      values:\n        key1:\n          value: value1\n        key2:\n          expiration: 10s\n          value: value2\n    sets:\n      values:\n        set1:\n          $extend: childSetTemplate\n          expiration: 10s\n          values:\n            - value: a\n            - value: b\n        set3:\n          expiration: 5s\n          values:\n            - value: x\n            - value: y\n    hashes:\n      values:\n        map1:\n          $extend: childHashTemplate\n          values:\n            - key: a\n              value: 1\n            - key: b\n              value: 2\n        map2:\n          values:\n            - key: c\n              value: 3\n            - key: d\n              value: 4\n    lists:\n      values:\n        list1:\n          $extend: childListTemplate\n          values:\n            - value: 1\n            - value: 100\n            - value: 200\n    zsets:\n      values:\n        zset1:\n          $extend: childZSetTemplate\n          values:\n            - value: 5\n              score: 10.1\n  2:\n    keys:\n      values:\n        key3:\n          value: value3\n        key4:\n          expiration: 5s\n          value: value4\n```\n\n## Моки\n\nЧтобы для тестов имитировать ответы от внешних сервисов, применяются моки.\n\nОдин мок - это поднятый \"на лету\" веб-сервер, который перед запуском каждого теста наполняется определенной логикой. Логика определяет, что ответит сервер на тот или иной запрос. Логика ответов описывается в файле теста.\n\n### Запуск моков при использовании gonkey как библиотеки\n\nПеред запуском тестов происходит старт всех планируемых к использованию моков - то есть поднимается заданное количество серверов, для каждого из них выделяется случайный порт.\n\n```go\n// создаем пустые моки сервисов\nm := mocks.NewNop(\n    \"cart\",\n    \"loyalty\",\n    \"catalog\",\n    \"madmin\",\n    \"okz\",\n    \"discounts\",\n)\n\n// запускаем моки\nerr := m.Start()\nif err != nil {\n    t.Fatal(err)\n}\ndefer m.Shutdown()\n```\n\nПосле того, как веб-серверы моков подняты, можно получить от них адреса (хост и порт), на которых они разместились. Используя эти адреса, вы конфигурируете свой сервис, чтобы вместо обращений к реальным системам он обращался к поднятым мок-серверам.\n\n```go\n// конфигурируем и запускаем наш сервис\nsrv := server.NewServer(\u0026server.Config{\n CartAddr:      m.Service(\"cart\").ServerAddr(),\n LoyaltyAddr:   m.Service(\"loyalty\").ServerAddr(),\n CatalogAddr:   m.Service(\"catalog\").ServerAddr(),\n MadminAddr:    m.Service(\"madmin\").ServerAddr(),\n OkzAddr:       m.Service(\"okz\").ServerAddr(),\n DiscountsAddr: m.Service(\"discounts\").ServerAddr(),\n})\ndefer srv.Close()\n```\n\nКак только вы подняли моки и сконфигурировали свой сервис, можно запускать тесты.\n\n```go\nrunner.RunWithTesting(t, \u0026runner.RunWithTestingParams{\n    Server:    srv,\n    Directory: \"tests/cases\",\n    Mocks:     m, // передаем моки в раннер тестов\n})\n```\n\nДополнительно библиотека регистрирует специальные переменные окружения `GONKEY_MOCK_\u003cИМЯ_MOCK\u003e`, которые содержат адрес и порт соответствующего мок-сервера. Эти переменные окружения вы можете использовать при написании тестов.\n\n### Описание моков в файле с тестом\n\nКаждый тест перед запуском сообщает мок-серверу конфигурацию, которая определяет, что мок-сервер ответит на тот или иной запрос. Эта конфигурация задается в YAML-файле с тестом в секции `mocks`.\n\nОдновременно в файле с тестом можно описать любое количество мок-сервисов:\n\n```yaml\n- name: Test with mocks\n  ...\n  mocks:\n    service1:\n      ...\n    service2:\n      ...\n    service3:\n      ...\n  request:\n    ...\n```\n\nОписание каждого мок-сервиса состоит из:\n\n`requestConstraints` - массив проверок, которые применяются к полученному запросу. Если хотя бы одна проверка не пройдена, тест считается проваленным. Список возможных проверок - ниже.\n\n`strategy` - стратегия ответа мока на запросы. Список возможных стратегий - ниже.\n\nОстальные ключи на первом уровне вложенности в описании мока - это параметры к стратегии. Их набор различен для каждой конкретной стратегии.\n\nПример конфигурации одного мок-сервиса:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      requestConstraints:\n        - ...\n        - ...\n      strategy: strategyName\n      strategyParam1: ...\n      strategyParam2: ...\n    ...\n```\n\n#### Проверки запросов (requestConstraints)\n\nЗапросы к мок-сервису можно валидировать с помощью одной или нескольких описанных ниже проверок.\n\nОписание каждой проверки состоит из параметра `kind`, в котором указывается, что за проверка будет применена.\n\nВсе остальные ключи на этом уровне - это параметры проверки. У каждой проверки свой набор параметров.\n\n##### nop\n\nПустая проверка. Всегда проходит успешно.\n\nНет параметров.\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      requestConstraints:\n        - kind: nop\n    ...\n```\n\n##### bodyMatchesJSON\n\nПроверяет, что тело запроса - это JSON, который соответствует заданному в параметре `body`.\n\nПараметры:\n\n- `body` (обязательный) - JSON, с которым будет сверяться запрос. Все ключи на всех уровнях, определенные в этом параметре, должны присутвовать в теле запроса.\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      requestConstraints:\n        # эта проверка будет требовать, чтобы запрос содержал ключи key1, key2 и subKey1,\n        # а значения были равны value1 и value2. Однако в запросе допускаются другие ключи,\n        # не перечисленные здесь - это нормально.\n        - kind: bodyMatchesJSON\n          body: \u003e\n            {\n              \"key1\": \"value1\",\n              \"key2\": {\n                \"subKey1\": \"value2\",\n              }\n            }\n    ...\n```\n\n##### bodyJSONFieldMatchesJSON\n\nКогда тело запроса есть JSON, проверяет что конкретное поле этого JSON является строкой,\nсодержащей в себе другой JSON, соответствующий заданному.\n\nПараметры:\n\n- `path` (обязательный) - путь к JSON-полю, в котором находится проверяемое значение.\n- `value` (обязательный) - JSON, с которым сверяется значение поля.\n\nПример:\n\nОригинальный запрос содержит вложенный JSON\n\n```yaml\n  {\n      \"field1\": {\n        \"field2\": \"{\\\"wrapped\\\": \\\"json\\\"}\"\n      }\n  }\n```\n\nПроверка вложенного JSON\n\n```yaml\n  ...\n  mocks:\n    service1:\n      requestConstraints:\n        - kind: bodyJSONFieldMatchesJSON\n          path: field1.field2\n          value: |\n            {\n              \"wrapped\": \"json\"\n            }\n  ...\n```\n\n##### pathMatches\n\nПроверяет, что путь в запросе соответствует заданному значению.\n\nПараметры:\n\n- `path` - строка, которой должен быть равен путь запроса;\n- `regexp` - регулярное выражение, которому должен соответствовать путь запроса.\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      requestConstraints:\n        - kind: pathMatches\n          path: /api/v1/test/somevalue\n    service2:\n      requestConstraints:\n        - kind: pathMatches\n          regexp: ^/api/v1/test/.*$\n    ...\n```\n\n##### queryMatches\n\nПроверяет, что параметры GET запроса соответствуют заданным в параметре `query`.\n\nПараметры:\n\n- `expectedQuery` (обязательный) - строка параметров с которой будет сверяться запрос. Порядок параметров не имеет значения.\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      requestConstraints:\n        # эта проверка будет требовать, чтобы запрос содержал ключи key1 и key2,\n        # а значения были равны key1=value1, key1=value11 и key2=value2. Ключи не указанные в запросе будут пропущены при проверке.\n        - kind: queryMatches\n          expectedQuery:  key1=value1\u0026key2=value2\u0026key1=value11\n    ...\n```\n\n##### queryMatchesRegexp\n\nРасширяет `queryMatches` так, чтобы можно было использовать проверку по регулярному выражению.\n\nПараметры:\n\n- `expectedQuery` (обязательный) - строка параметров с которой будет сверяться запрос. Порядок параметров не имеет значения.\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      requestConstraints:\n        # работает аналогично queryMatches с добавлением возможности вызова $matchRegexp\n        - kind: queryMatchesRegexp\n          expectedQuery:  key1=value1\u0026key2=$matchRegexp(\\\\d+)\u0026key1=value11\n    ...\n```\n\n##### methodIs\n\nПроверяет, что метод запроса соответствует заданному.\n\nПараметры:\n\n- `method` (обязательный) - строка, с которой сравнивается метод запроса.\n\nЕсть также два коротких варианта, не требущих указания параметра `method`:\n\n- `methodIsGET`\n- `methodIsPOST`\n\nПримеры:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      requestConstraints:\n        - kind: methodIs\n          method: PUT\n    service2:\n      requestConstraints:\n        - kind: methodIsPOST\n    ...\n```\n\n##### headerIs\n\nПроверяет, что в запросе есть указанный заголовок и, опционально, что его значение равно заданному или подпадает под условия регулярного выражения.\n\nПараметры:\n\n- `header` (обязательный) - название заголовка, который ожидается в запросе;\n- `value` - строка, которой должно быть равно значение заголовка;\n- `regexp` - регулярное выражение, которому должно соответствовать значение заголовка.\n\nПримеры:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      requestConstraints:\n        - kind: headerIs\n          header: Content-Type\n          value: application/json\n    service2:\n      requestConstraints:\n        - kind: headerIs\n          header: Content-Type\n          regexp: ^(application/json|text/plain)$\n    ...\n```\n\n##### bodyMatchesText\n\nПроверяет что, тело запроса соответствует указанному или подпадает под условия регулярного выражения.\n\nПараметры:\n\n- `body` - строка, которой должно быть равно значение тела запроса;\n- `regexp` - регулярное выражение, которому должно соответствовать значение тела.\n\nПримеры:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      requestConstraints:\n        - kind: bodyMatchesText\n            body: |-\n              query HeroNameAndFriends {\n                    hero {\n                      name\n                      friends {\n                        name\n                      }\n                    }\n                  }\n    service2:\n      requestConstraints:\n        - kind: bodyMatchesText\n            regexp: (HeroNameAndFriendsq)\n    ...\n```\n\n##### bodyMatchesXML\n\nПроверяет, что тело запроса - это XML, который соответствует заданному в параметре `body`.\n\nПараметры:\n\n- `body` (обязательный) - XML, с которым будет сверяться запрос.\n\nExample:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      requestConstraints:\n        - kind: bodyMatchesXML\n          body: |\n            \u003cPerson\u003e\n              \u003cCompany\u003eHogwarts School of Witchcraft and Wizardry\u003c/Company\u003e\n              \u003cFullName\u003eHarry Potter\u003c/FullName\u003e\n              \u003cEmail where=\"work\"\u003ehpotter@hog.gb\u003c/Email\u003e\n              \u003cEmail where=\"home\"\u003ehpotter@gmail.com\u003c/Email\u003e\n              \u003cAddr\u003e4 Privet Drive\u003c/Addr\u003e\n              \u003cGroup\u003e\n                \u003cValue\u003eHexes\u003c/Value\u003e\n                \u003cValue\u003eJinxes\u003c/Value\u003e\n                \u003cValue\u003eJinxes\u003c/Value\u003e\n              \u003c/Group\u003e\n            \u003c/Person\u003e\n  ...\n```\n\n#### Стратегии ответов (strategy)\n\nСтратегии ответов определяют, как мок будет отвечать на входящие запросы.\n\n##### nop\n\nПустая стратегия. На любой запрос возвращается ответ `204 No Content` с пустым телом.\n\nНе имеет параметров.\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      strategy: nop\n    ...\n```\n\n##### file\n\nВозвращает ответ, прочитанный из файла.\n\nПараметры:\n\n- `filename` (обязательный) - имя файла, из которого будет прочитано тело ответа;\n- `statusCode` - HTTP-код ответа, по умолчанию `200`;\n- `headers` - заголовки ответа.\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      strategy: file\n      filename: responses/service1_success.json\n      statusCode: 500\n      headers:\n        Content-Type: application/json\n    ...\n```\n\n##### constant\n\nВозвращает заданный ответ.\n\nПараметры:\n\n- `body` (обязательный) - задает тело ответа;\n- `statusCode` - HTTP-код ответа, по умолчанию `200`;\n- `headers` - заголовки ответа.\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      strategy: constant\n      body: \u003e\n        {\n          \"status\": \"error\",\n          \"errorCode\": -32884,\n          \"errorMessage\": \"Internal error\"\n        }\n      statusCode: 500\n    ...\n```\n\n##### template\n\nСтратегия дает возможность использовать в теле ответа, параметры входящего в мок запроса.\nРеализовано с использованием пакета [text/template](https://pkg.go.dev/text/template).\nАвтоматически подгружает запрос в переменную `request`.\n\nПараметры:\n\n- `body` (обязательный) - задает тело ответа, должно быть совместимо с `text/template`;\n- `statusCode` - HTTP-код ответа, по умолчанию `200`;\n- `headers` - заголовки ответа.\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      strategy: template\n      body: \u003e\n        {\n          \"value-from-query\": {{ .request.Query \"value\" }},\n          \"data-from-body\": {{ default 10 .request.Json.data }}\n        }\n      statusCode: 200\n    ...\n```\n\n##### uriVary\n\nИспользует разные стратегии ответа, в зависимости от пути запрашиваемого ресурса.\n\nПри получении запроса на ресурс, который не задан в параметрах, тест считается проваленным.\n\nПараметры:\n\n- `uris` (обязательный) - список ресурсов, каждый ресурс можно сконфигурировать как отдельный мок-сервис, используя любые доступные проверки запросов и стратегии ответов (см. пример)\n- `basePath` - общий базовый путь для всех ресурсов, по умолчанию пустой\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      strategy: uriVary\n      basePath: /v2\n      uris:\n        /shelf/books:\n          strategy: file\n          filename: responses/books_list.json\n          statusCode: 200\n        /shelf/books/1:\n          strategy: constant\n          body: \u003e\n            {\n              \"error\": \"book not found\"\n            }\n          statusCode: 404\n    ...\n```\n\n##### methodVary\n\nИспользует разные стратегии ответа, в зависимости от метода запроса.\n\nПри получении запроса методом, который не упомянут в methodVary, тест считается проваленным.\n\nПараметры:\n\n- `methods` (обязательный) - список методов, каждый из которых можно сконфигурировать как отдельный мок-сервис, используя любые доступные проверки запросов и стратегии ответов (см. пример)\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      strategy: methodVary\n      methods:\n        GET:\n          # ничего не мешает в этом месте использовать стратегию `uriVary`\n          # тем самым можно формировать разные ответы на комбинацию метод+ресурс\n          strategy: constant\n          body: \u003e\n            {\n              \"error\": \"book not found\"\n            }\n          statusCode: 404\n        POST:\n          strategy: nop\n          statusCode: 204\n    ...\n```\n\n##### sequence\n\nНа каждый последующий запрос эта стратегия будет отвечать так, как определено в очередной дочерней стратегии.\n\nЕсли для запроса не задано дочерней стратегии, то есть пришло больше запросов, чем задано стратегий, то тест считается проваленным.\n\nПараметры:\n\n- `sequence` (обязательный) - список дочерних стратегий.\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      strategy: sequence\n      sequence:\n        # Отвечает разным текстом на каждый последующий запрос:\n        # на первый запрос - \"1\", на второй - \"2\" и так далее.\n        # Ответ на пятый и последующие запросы будет 404 Not Found.\n        - strategy: constant\n          body: '1'\n        - strategy: constant\n          body: '2'\n        - strategy: constant\n          body: '3'\n        - strategy: constant\n          body: '4'\n    ...\n```\n\n##### basedOnRequest\n\nИспользует разные стратегии ответа, в зависимости от пути запрашиваемого ресурса. Разрешает объявлять несколько стратегий для одного пути. Конкурентно безопасен.\n\nПри получении запроса на ресурс, который не задан в параметрах, тест считается проваленным.\n\nПараметры:\n\n- `uris` (обязательный) - список ресурсов, каждый ресурс можно сконфигурировать как отдельный мок-сервис, используя любые доступные проверки запросов и стратегии ответов (см. пример)\n\nExample:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      strategy: basedOnRequest\n      uris:\n        - strategy: constant\n          body: \u003e\n            {\n              \"ok\": true\n            }\n          requestConstraints:\n            - kind: queryMatches\n              expectedQuery: \"key=value1\"\n            - kind: pathMatches\n              path: /request\n        - strategy: constant\n          body: \u003e\n            {\n             \"ok\": true\n            }\n          requestConstraints:\n            - kind: queryMatches\n              expectedQuery: \"key=value2\"\n            - kind: pathMatches\n              path: /request\n    ...\n```\n\n##### dropRequest\n\nСтратегия, которая по умолчанию на любой запрос сбрасывает соединение. Используется для эмуляции проблем с сетью.\n\nНе имеет параметров.\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      strategy: dropRequest\n    ...\n```\n\n#### Подсчет количества вызовов\n\nВы можете указать, сколько раз должен быть вызван мок или отдельный ресурс мока (используя `uriVary`). Если фактическое количество вызовов будет отличаться от ожидаемого, тест будет считаться проваленным.\n\nПример:\n\n```yaml\n  ...\n  mocks:\n    service1:\n      # должен вызываться ровно один раз\n      calls: 1\n      strategy: file\n      filename: responses/books_list.json\n  ...\n```\n\n```yaml\n  ...\n  mocks:\n    service1:\n      strategy: uriVary\n      uris:\n        /shelf/books:\n          # должен вызываться ровно один раз\n          calls: 1\n          strategy: file\n          filename: responses/books_list.json\n  ...\n```\n\n## Использование shell скриптов\n\nПри запуске теста, операции выполняются в следующем порядке:\n\n1. Загрузка фикстур\n2. Запуск моков\n3. Выполнение beforeScript\n4. HTTP-запрос\n5. afterRequestScript\n6. Выполнение проверок\n\n### Описание скрипта\n\nДля описания скрипта нужно указать два параметра:\n\n- `path` (обязательный) - строка, указывает путь к файлу скрипта.\n- `timeout` - время в секундах, отвечает за завершение скрипта по таймауту. По-умолчанию таймаут будет равен `3`.\n\nПример:\n\n```yaml\n  ...\n  afterRequestScript:\n    path: './cli_scripts/cmd_recalculate.sh'\n    # таймаут будет равен 10с\n    timeout: 10\n  ...\n```\n\n```yaml\n  ...\n  beforeScript:\n    path: './cli_scripts/cmd_recalculate.sh'\n    # таймаут будет равен 10с\n    timeout: 10\n  ...\n```\n\n```yaml\n  ...\n  beforeScript:\n    path: './cli_scripts/cmd_recalculate.sh'\n    # таймаут будет равен 3с\n  ...\n```\n\n### Запуск скрипта с параметризацией\n\nВ случае когда тесты используют параметризированные запросы также можно использовать различные скрипты для каждого запуска теста.\n\nПример:\n\n```yaml\n  ...\n  beforeScript:\n    path: |\n      ./cli_scripts/{{.file_name}}\n  ...\n  cases:\n    - requestArgs:\n        customer_id: 1\n        customer_email: \"customer_1_recalculate@lamoda.ru\"\n      responseArgs:\n        200:\n          rrr: 1\n          in_transit: 1\n      beforeScriptArgs:\n        file_name: \"cmd_recalculate_customer_1.sh\"\n```\n\n## Запрос в Базу данных\n\nПосле выполнения http запросов можно выполнить SQL запрос в БД для проверки изменений данных.\nДопускается что ответ может содержать несколько записей. Далее эти данные сравниваются с ожидаемым списком записей.\n\n### Формат описания запросов\n\nДля описания запросов к БД в тесте, можно использовать legacy-формат:\n\n```yaml\n- name: my test\n  ...\n  dbQuery: \u003e\n    SELECT ...\n  dbResponse:\n    - ...\n    - ...\n```\n\nНо, более предпочтительным будет следующий формат:\n\n```yaml\n- name: my test\n  ...\n  dbChecks:\n    - dbQuery: \u003e\n        SELECT ...\n      dbResponse:\n        - ...\n```\n\nТак как он, дает возможность выполнять более 1-го запроса в БД.\n*ВАЖНО*: Все описанные ниже техники шаблонизации запросов, работают в обоих случаях описания теста.\n\n### Описание запроса\n\nПод запросом подразумевается SELECT, который вернет любое количество строк.\n\n- `dbQuery` - строка, содержит SQL запрос\n\nПример:\n\n```yaml\n  ...\n  dbQuery:\n    SELECT code, purchase_date, partner_id FROM mark_paid_schedule AS m WHERE m.code = 'GIFT100000-000002'\n  ...\n```\n\n### Описание ответа на запрос в Базу данных\n\nПод ответом подразумевается список json объектов которые должен вернуть запрос в БД.\n\n- `dbResponse` - строка, содержит список json объектов\n\nПример:\n\n```yaml\n  ...\n  dbResponse:\n    - '{\"code\":\"GIFT100000-000002\",\"purchase_date\":\"2330-02-02T13:15:11.912874\",\"partner_id\":1}'\n    - '{\"code\":\"GIFT100000-000003\",\"purchase_date\":\"2330-02-02T13:15:11.912874\",\"partner_id\":1}'\n    - '{\"code\":\"$matchRegexp(GIFT([0-9]{6})-([0-9]{6}))\",\"purchase_date\":\"2330-02-02T13:15:11.912874\",\"partner_id\":1}'\n```\n\nКак видно из примера, вы можете использовать Regexp для проверки ответа БД.\n\n```yaml\n  ...\n  dbResponse:\n    # пустой список\n```\n\n### Параметризация при запросах в Базу данных\n\nКак и в случае с телом http-запроса, мы можем использовать параметризированные запросы.\n\nПример:\n\n```yaml\n  ...\n    dbQuery: \u003e\n      SELECT code, partner_id FROM mark_paid_schedule AS m WHERE DATE(m.purchase_date) BETWEEN '{{ .fromDate }}' AND '{{ .toDate }}'\n\n    dbResponse:\n      - '{\"code\":\"{{ .cert1 }}\",\"partner_id\":1}'\n      - '{\"code\":\"{{ .cert2 }}\",\"partner_id\":1}'\n  ...\n    cases:\n      ...\n      dbQueryArgs:\n        fromDate: \"2330-02-01\"\n        toDate: \"2330-02-05\"\n      dbResponseArgs:\n        cert1: \"GIFT100000-000002\"\n        cert2: \"GIFT100000-000003\"\n```\n\nВ случае, когда в разных тестах ответ содержит разное количество записей, вы можете переопределить ответ целиком для конкретного теста\nпродолжая использовать шаблон с параметрами в остальных\n\nПример:\n\n```yaml\n  ...\n    dbQuery: \u003e\n      SELECT code, partner_id FROM mark_paid_schedule AS m WHERE DATE(m.purchase_date) BETWEEN '{{ .fromDate }}' AND '{{ .toDate }}'\n\n    dbResponse:\n      - '{\"code\":\"{{ .cert1 }}\",\"partner_id\":1}'\n  ...\n    cases:\n      ...\n      dbQueryArgs:\n        fromDate: \"2330-02-01\"\n        toDate: \"2330-02-05\"\n      dbResponseArgs:\n        cert1: \"GIFT100000-000002\"\n      ...\n      dbQueryArgs:\n        fromDate: \"2330-02-01\"\n        toDate: \"2330-02-05\"\n      dbResponseFull:\n        - '{\"code\":\"GIFT100000-000002\",\"partner_id\":1}'\n        - '{\"code\":\"GIFT100000-000003\",\"partner_id\":1}'\n```\n\n### Игнорирование порядка записей в ответе на запрос в базу данных\n\nМожно использовать флаг `ignoreDbOrdering` в секции `comparisonParams` для включения/выключения функционала проверки полученных строк в ответе от базы данных не по порядку.\nЭто может пригодиться для обхода использования оператора `ORDER BY` в запросах.\n\n- `ignoreDbOrdering` - значение true/false.\n\nПример:\n\n```yaml\n  comparisonParams:\n    ignoreDbOrdering: true\n  ...\n  dbQuery: \u003e\n    SELECT id, name, surname FROM users LIMIT 2\n    \n  dbResponse:\n    - '{ \"id\": 2, \"name\": \"John\", \"surname\": \"Doe\" }'\n    - '{ \"id\": 1, \"name\": \"Jane\", \"surname\": \"Doe\" }'\n```\n\n## JSON-schema\n\nДля упрощения написания тестов на Gonkey, используйте [файл со схемой](https://raw.githubusercontent.com/lamoda/gonkey/master/gonkey.json)\n\nОн добавляет in-line документацию и авто-дополнение в IDE которые это поддерживают.\n\nПример работы в IDE Jetbrains:\n![Example Jetbrains](https://i.imgur.com/oYuPuR3.gif)\n\nПример работы в IDE VSCode:\n![Example Jetbrains](https://i.imgur.com/hBIGjP9.gif)\n\n### Настройка на IDE Jetbrains\n\nСкачайте [файл со схемой](https://raw.githubusercontent.com/lamoda/gonkey/master/gonkey.json).\nВ настройках Languages \u0026 Frameworks \u003e Schemas and DTDs \u003e JSON Schema Mappings\n\n![Jetbrains IDE Settings](https://i.imgur.com/xkO22by.png)\n\nДобавьте новую схему\n\n![Add schema](https://i.imgur.com/XHw14GJ.png)\n\nЗадайте имя схемы, выберите скачанный файл, а также выберите версию схем Draft 7\n\n![Name, file, version](https://i.imgur.com/LfJfis0.png)\n\nПосле этого добавьте маппинг. Можно выбрать файл, папку с тестами или маску.\n\n![Mapping](https://i.imgur.com/iFjm0Ld.png)\n\nВыберите то что удобно для вас.\n\n![Mapping pattern](https://i.imgur.com/WIK6sZW.png)\n\nПосле этого сохраните настройки, и если вы всё сделали правильно в нижнем правом углу окна IDE не должно\nотображатся No JSON Schema\n![No Schema](https://i.imgur.com/zLqv1Zv.png)\n\nА должно быть ваше название схемы.\n\n![Schema Name](https://i.imgur.com/DDXdCO7.png)\n\n### Настройка на IDE VSCode\n\nДля начала вам нужно установить плагин для работы с YAML\nОткройте меню Code(File)-\u003ePreferences-\u003eExtensions\n\n![VSCode Preferences](https://i.imgur.com/X7bk5Kh.png)\n\nНаберите в поиске YAML, и установите расширение YAML Language Support by Red Hat\n\n![Yaml Extension](https://i.imgur.com/57onioF.png)\n\nОткройте меню Code(File)-\u003ePreferences-\u003eSettings\nНаберите YAML:Schemas и нажмите на ссылку *Edit in settings.json*\n![Yaml link](https://i.imgur.com/IEwxWyG.png)\n\nДобавьте маппинг файла и путь к схеме\n\n```\n\"yaml.schemas\": {\n  \"C:\\\\Users\\\\Leo\\\\gonkey.json\": [\"*.gonkey.yaml\"]          \n}\n```\n\nВ примере выше, схема из файла C:\\Users\\Leo\\gonkey.json будет применяться ко всем файлам\nс расширением .gonkey.yaml\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flamoda%2Fgonkey","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flamoda%2Fgonkey","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flamoda%2Fgonkey/lists"}