vyvchy
    Теми розділу

    03 · Веб і мережі для AQA

    REST API та формати даних

    Зміст

    Веб-інтерфейс, який ви тестуєте очима, — це лише вершина. Під кнопками й формами живе інший, значно швидший і стабільніший світ: HTTP-запити до серверного API. Розуміти цей світ для AQA не опція, а базова грамотність. API-тести менш крихкі за UI, дають точний контроль над станом системи й дозволяють підготувати дані для сценарію за мілісекунди замість кліків через півсторінки. Цей розділ пояснює, як влаштований найпоширеніший стиль веб-API — REST, — і що з ним робити в автотестах.

    Що таке REST і навіщо він потрібен

    REST (Representational State Transfer) — це не протокол і не стандарт, а архітектурний стиль, який Рой Філдінг (Roy Fielding) описав у своїй докторській дисертації 2000 року. Слово «стиль» тут ключове: REST не змушує використовувати конкретні заголовки чи формати, він задає набір обмежень (constraints), і система, що їх дотримується, вважається «RESTful».

    Обмеження такі:

    • Клієнт-сервер (client-server). Клієнт (браузер, мобільний застосунок, ваш автотест) і сервер розділені. Вони спілкуються лише через чітко визначений інтерфейс, тож можуть розвиватися незалежно.
    • Відсутність стану (statelessness). Сервер не памʼятає нічого між запитами. Кожен запит містить усе потрібне для його обробки — токен авторизації, параметри, тіло. Це прямо впливає на тести: не існує «сесії», яку треба вибудовувати клацаннями; кожен виклик самодостатній, а отже, його легко повторити чи запустити ізольовано.
    • Кешованість (cacheability). Відповіді можуть позначатися як кешовані або ні, щоб клієнти й проміжні вузли не смикали сервер зайвий раз.
    • Однаковий інтерфейс (uniform interface). Ресурси адресуються однотипно (через URL), маніпуляції над ними — стандартними HTTP-методами, а повідомлення самоописові. Сюди ж належить HATEOAS (Hypermedia as the Engine of Application State) — ідея, що відповідь несе посилання на доступні далі дії, тож клієнт «мандрує» API за наданими лінками, а не хардкодить URL. На практиці HATEOAS реалізують рідко, і більшість «REST API» його ігнорують.
    • Багатошаровість (layered system). Між клієнтом і сервером можуть стояти проксі, балансувальники, шлюзи — клієнт про них не знає.
    • Код на вимогу (code on demand) — єдине необовʼязкове обмеження: сервер може віддавати виконуваний код (наприклад, JavaScript).

    Практичний висновок для тестувальника: якщо API справді дотримується цих обмежень, ви можете розраховувати на передбачувану поведінку — однакові URL, однакові методи, самодостатні запити. На практиці багато «REST API» є RESTful лише частково, і це нормально: терміном часто називають будь-який HTTP-API з JSON-відповідями.

    Ресурс і ендпоінт

    Ресурс (resource) — це будь-яка сутність, якою оперує система: користувач, замовлення, тест-ран, файл. У REST мислять іменниками, а не дієсловами. Немає ендпоінта «створити користувача»; є ресурс «користувачі», над яким виконують дію методом.

    Ендпоінт (endpoint) — конкретна URL-адреса, за якою доступний ресурс або колекція ресурсів. Порівняйте:

    URLЩо це
    /api/v1/usersколекція всіх користувачів
    /api/v1/users/42конкретний користувач з id 42
    /api/v1/users/42/ordersзамовлення користувача 42 (вкладений ресурс)
    /api/v1/orders?status=paidколекція замовлень із фільтром через query-параметр

    Зверніть увагу на конвенції: колекції — множина, ідентифікатор іде окремим сегментом шляху, а фільтри/сортування/пагінація — через параметри рядка запиту (query string) після ?. Гарний REST-URL не містить дієслів (/getUser, /createOrder — антипатерн): дію задає HTTP-метод.

    HTTP-методи й відповідність CRUD

    CRUD — це чотири базові операції над даними: Create, Read, Update, Delete. REST відображає їх на HTTP-методи так:

    ОпераціяМетодПрикладБезпечний (safe)?Ідемпотентний (idempotent)?
    ReadGETGET /users/42тактак
    CreatePOSTPOST /usersніні
    Update (повна заміна)PUTPUT /users/42нітак
    Update (часткова)PATCHPATCH /users/42ніне обовʼязково
    DeleteDELETEDELETE /users/42нітак

    Ще є HEAD (як GET, але без тіла — лише заголовки) та OPTIONS (запит про можливості ресурсу, використовується в CORS-preflight).

    Два поняття з таблиці варто розкрити, бо вони прямо стосуються надійності тестів.

    Безпечний метод (safe) — той, що не змінює стан на сервері. GET лише читає, тож у тесті GET-запити можна повторювати скільки завгодно без побічних ефектів.

    Ідемпотентний метод (idempotent) — той, який при повторному виклику з тими самими даними дає той самий кінцевий стан. DELETE /users/42, викликаний двічі, залишає систему в тому ж стані: користувача немає (хоча статус-код другого виклику може бути іншим — про це нижче). PUT замінює ресурс цілком, тож повтор не додає нічого нового. А POST не ідемпотентний: два POST /users створять двох користувачів.

    Точні визначення safe та idempotent для кожного методу закріплені в HTTP-специфікації (RFC 9110 «HTTP Semantics»). При написанні контрактних тестів звіряйтеся з нею для крайових випадків — зокрема поведінки PATCH, ідемпотентність якого залежить від семантики самої зміни.

    JSON: типи даних

    JSON (JavaScript Object Notation) — домінантний формат обміну в сучасних REST API. Він текстовий, читабельний і має шість типів значень: чотири прості (рядок, число, булеве значення, null) і два структурні (обʼєкт, масив).

    {
      "string": "рядок у подвійних лапках",
      "number": 42.5,
      "boolean": true,
      "nothing": null,
      "object": { "key": "value" },
      "array": [1, 2, 3]
    }

    Кілька особливостей, які регулярно спотикають початківців:

    • Числа не поділяються на цілі й дробові. У JSON є один тип number — жодних окремих int чи float. Це важливо для перевірок: не покладайтеся сліпо на те, що поле прийде саме цілим — залежить від того, як його серіалізував бекенд.
    • Немає типу «дата». Дати передають рядком (найчастіше у форматі ISO 8601, наприклад "2026-07-08T10:15:30Z") або числом (Unix-час). Формат — частина контракту, і його треба перевіряти явно.
    • Ключі обʼєктів — завжди рядки в подвійних лапках. Одинарні лапки недопустимі.
    • Немає коментарів. Коментар у файлі, що претендує на JSON, зробить його невалідним.
    • null — це значення, а не відсутність поля. Розрізняйте {"phone": null} (поле є, значення порожнє) і {} (поля немає взагалі) — для тестів це різні контракти.

    Обʼєкт vs масив і вкладеність

    Дві структури-контейнери JSON плутають найчастіше. Обʼєкт (object) — невпорядкований набір пар «ключ-значення» у фігурних дужках {}. Масив (array) — впорядкований список значень у квадратних [].

    Практичне правило: якщо ви звертаєтесь до елемента за назвою — це обʼєкт (user.email); якщо за позицією — масив (users[0]). Реальні відповіді комбінують обидві структури й бувають глибоко вкладеними:

    {
      "id": 42,
      "name": "Alice",
      "roles": ["admin", "qa"],
      "profile": {
        "city": "Kyiv",
        "contacts": [
          { "type": "email", "value": "alice@example.com" },
          { "type": "phone", "value": "+380..." }
        ]
      }
    }

    Тут roles — масив рядків, profile — вкладений обʼєкт, а profile.contacts — масив обʼєктів. Шлях до першого email виглядатиме як profile.contacts[0].value. Уміння впевнено читати такі шляхи — щоденний навик при написанні асертів і при роботі з JSONPath у тестових інструментах.

    Важлива дрібниця для стабільності тестів: порядок ключів в обʼєкті не гарантований, а порядок елементів масиву — значущий. Тому не пишіть асерт, що покладається на послідовність ключів обʼєкта; а якщо API повертає масив без явного сортування, порядок його елементів може «плавати» від запиту до запиту й давати флак (flakiness). У такому разі сортуйте колекцію в тесті перед порівнянням або перевіряйте наявність елементів без прив’язки до позиції.

    Серіалізація й десеріалізація

    Серіалізація (serialization) — перетворення обʼєкта в памʼяті (структури мови програмування) на текст для передавання мережею. Десеріалізація (deserialization) — зворотний процес: текст перетворюється на обʼєкт, з яким зручно працювати в коді.

    У JavaScript це два вбудованих методи:

    const user = { id: 42, name: "Alice" };
    
    const text = JSON.stringify(user);      // серіалізація → '{"id":42,"name":"Alice"}'
    const back = JSON.parse(text);          // десеріалізація → знову обʼєкт

    JSON.stringify

    передача мережею

    JSON.parse

    Обʼєкт у памʼяті

    Текст JSON

    Текст JSON

    Обʼєкт у памʼяті

    JSON.stringify

    передача мережею

    JSON.parse

    Обʼєкт у памʼяті

    Текст JSON

    Текст JSON

    Обʼєкт у памʼяті

    Чому це не абстрактна теорія для AQA: майже кожен API-тест виконує десеріалізацію тіла відповіді, щоб дістатися до полів. Тут ховаються типові помилки. JSON.parse кине виняток на невалідному JSON — а порожнє тіло, HTML-сторінка помилки замість JSON чи обірваний потік саме такими і є. Тому надійний тест спершу перевіряє Content-Type та статус, і лише потім парсить тіло — інакше падіння на парсингу замаскує справжню причину. Ще нюанс: при серіалізації JavaScript перетворює Date на рядок ISO 8601, а undefined-поля просто зникають — про це варто памʼятати, готуючи тіло запиту.

    JSON проти XML

    До панування JSON у вебсервісах був XML (Extensible Markup Language), і ви досі зустрічатимете його в legacy-системах, SOAP-сервісах та деяких банківських/урядових інтеграціях.

    <user id="42">
      <name>Alice</name>
      <roles>
        <role>admin</role>
        <role>qa</role>
      </roles>
    </user>
    КритерійJSONXML
    Багатослівністькомпактнийбільш громіздкий (закривні теги)
    Типи данихвбудовані (number, boolean, null…)усе текст, типи задає схема
    Атрибутинемає, лише пари ключ-значенняє атрибути + вміст тегів
    Схема/валідаціяJSON SchemaXSD, DTD
    Коментарінемаєє
    Простори іменнемаєє (namespaces)
    Пошук по документуJSONPathXPath
    Типове застосуванняREST, сучасні веб-APISOAP, legacy, документо-орієнтовані формати

    Для тестувальника головна відмінність практична: XML має атрибути й простори імен, тож той самий «шлях до значення» в XML описується через XPath, а в JSON — через крапкову нотацію або JSONPath. Якщо тестуєте XML-API, готуйтеся працювати з XPath-локаторами по відповіді — логіка та сама, що й у DOM.

    application/x-www-form-urlencoded проти JSON-тіла

    Коли клієнт надсилає дані на сервер (POST, PUT, PATCH), формат тіла оголошується заголовком Content-Type. Два поширені варіанти для AQA — form-urlencoded і JSON.

    application/x-www-form-urlencoded — формат, у якому дані кодуються як пари ключ=значення, зʼєднані &, зі спецсимволами у percent-encoding. Пробіл у цьому форматі кодується символом + (декодери також приймають %20). Це те, що історично надсилає HTML-форма з method="post" без явного enctype.

    POST /login HTTP/1.1
    Host: example.com
    Content-Type: application/x-www-form-urlencoded
    
    username=alice&password=secret%21

    application/json — тіло є JSON-документом, здатним передати вкладеність і типи:

    POST /api/v1/users HTTP/1.1
    Host: api.example.com
    Content-Type: application/json
    
    { "name": "Alice", "roles": ["admin", "qa"] }
    form-urlencodedJSON
    Структураплоскі пари ключ-значеннявкладені обʼєкти й масиви
    Типиусе рядкиnumber, boolean, null, обʼєкти, масиви
    Спецсимволиpercent-encoding (пробіл — +)екранування в лапках
    Типове застосуванняформи логіну, прості дії, OAuth token-запитисучасні REST-API

    Головний висновок: Content-Type має відповідати реальному тілу. Класична причина, чому «в браузері працює, а в тесті ні», — тест надсилає JSON-тіло, але сервер очікує form-urlencoded (або навпаки), і повертає 400 чи 415 (Unsupported Media Type). Перш ніж воювати з тестом, підгляньте справжній запит у DevTools (вкладка Network) і скопіюйте Content-Type та формат тіла точно.

    Чому в тесті перевіряють і статус, і тіло

    Спокуса перевірити лише статус-код («прийшло 200 — значить ок») — джерело хибно-зелених тестів. Статус і тіло відповідають на різні запитання:

    • Статус-код (status code) каже, як сервер поставився до запиту на рівні протоколу: прийняв, відхилив, не знайшов, впав.
    • Тіло (body) каже, що саме повернулося: ті дані, ті значення, та структура.

    Приклади, чому обох потрібно разом:

    • Сервер повертає 200 OK, але тіло — {"error": "not found"} замість очікуваного обʼєкта (антипатерн, який трапляється частіше, ніж хотілось би).
    • Створення повернуло 201 Created, але поле id у тілі — null, тож наступний крок тесту не матиме з чим працювати.
    • Оновлення повернуло 200, тіло теж схоже на правильне, але змінилося не те поле.

    Мінімальний надійний асерт перевіряє три речі: статус, Content-Type і структуру/значення тіла.

    Ні

    Так

    Ні

    Так

    Ні

    Так

    Відповідь сервера

    Статус очікуваний?

    Фейл: статус

    Content-Type = JSON?

    Фейл: не парсити тіло

    Розпарсити тіло

    Структура й значення збігаються?

    Фейл: тіло

    Тест зелений

    Ні

    Так

    Ні

    Так

    Ні

    Так

    Відповідь сервера

    Статус очікуваний?

    Фейл: статус

    Content-Type = JSON?

    Фейл: не парсити тіло

    Розпарсити тіло

    Структура й значення збігаються?

    Фейл: тіло

    Тест зелений

    const res = await request.post('/api/v1/users', {
      data: { name: 'Alice', email: 'alice@example.com' },
    });
    
    expect(res.status()).toBe(201);
    expect(res.headers()['content-type']).toContain('application/json');
    
    const body = await res.json();
    expect(body).toMatchObject({
      id: expect.any(Number),
      name: 'Alice',
      email: 'alice@example.com',
    });

    Зверніть увагу на expect.any(Number): ми фіксуємо тип і наявність поля, не привʼязуючись до конкретного згенерованого id. Це навмисно — тест має бути стійким до значень, які система призначає сама, інакше він розсиплеться при першому ж перезапуску на чистій базі.

    Коди статусів, які варто знати напамʼять

    КодНазваЗначення для тесту
    200OKуспіх, тіло очікуване
    201Createdресурс створено; часто в тілі — новий обʼєкт, іноді заголовок Location
    204No Contentуспіх без тіла (типово для DELETE); не парсити тіло
    400Bad Requestнекоректний запит (структура/синтаксис)
    401Unauthorizedнемає/невалідна автентифікація
    403Forbiddenавтентифікація є, прав недостатньо
    404Not Foundресурсу немає
    409Conflictконфлікт стану (напр., дублікат)
    422Unprocessable Contentсинтаксис ок, але бізнес-валідація не пройдена
    429Too Many Requestsперевищено ліміт запитів
    500Internal Server Errorзбій на сервері
    503Service Unavailableсервіс тимчасово недоступний

    Розрізняйте 401 і 403 (не автентифікований проти не авторизований) та 400 і 422 (зламаний запит проти валідного за формою, але неприйнятного за бізнес-правилами) — ці пари постійно фігурують у негативних тест-кейсах.

    Пагінація: offset/limit і cursor

    Колекції бувають великими, тому API віддає їх порціями (сторінками). Дві поширені моделі.

    Offset/limit (зсув і ліміт). Клієнт каже «пропусти N записів, віддай наступні M».

    GET /api/v1/tests?offset=40&limit=20

    Проста й інтуїтивна модель, дозволяє стрибати на довільну сторінку. Слабке місце — нестабільність при даних, що змінюються: якщо між запитами двох сусідніх сторінок хтось вставив або видалив запис, ви можете пропустити елемент або отримати дубль. Для тестів це означає ризик флаку в паралельних прогонах на спільній базі.

    Cursor (курсор). Замість числового зсуву сервер віддає непрозорий маркер, що вказує на позицію в наборі, і клієнт передає його в наступному запиті.

    GET /api/v1/tests?limit=20
    GET /api/v1/tests?limit=20&cursor=eyJpZCI6MTIzfQ
    offset/limitcursor
    Перехід на довільну сторінкулегкозазвичай ні (лише вперед/назад)
    Стабільність при змінах данихслабкависока
    Читабельність параметразрозуміланепрозорий токен

    Що з цього корисно для тестів: коли перевіряєте пагінацію, не хардкодьте, що на певній сторінці лежить певний запис — на спільному чи «живому» середовищі дані змінюються. Перевіряйте інваріанти: розмір сторінки не перевищує limit; сусідні сторінки не перетинаються; курсор наступної сторінки присутній, поки є дані, і зникає (або приходить порожня сторінка) в кінці. Такі асерти не залежать від конкретного вмісту й не флакають.

    Rate limiting і статус 429

    Щоб захиститися від перевантаження, API обмежує кількість запитів на одиницю часу з одного клієнта чи ключа. При перевищенні ліміту сервер відповідає 429 Too Many Requests.

    HTTP/1.1 429 Too Many Requests
    Retry-After: 30
    Content-Type: application/json
    
    { "error": "rate limit exceeded" }
    СерверКлієнтСерверКлієнтПауза на Retry-AfterЗапити понад ліміт429 Too Many Requests, Retry-After: 30Повтор запиту200 OKСерверКлієнтСерверКлієнтПауза на Retry-AfterЗапити понад ліміт429 Too Many Requests, Retry-After: 30Повтор запиту200 OK

    Заголовок Retry-After підказує, скільки чекати перед повторною спробою (у секундах або як HTTP-дата). За специфікацією він необовʼязковий, але його часто надсилають. Багато API також віддають лічильники ліміту через заголовки на кшталт X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. Це де-факто конвенція, а не частина HTTP-стандарту: їхні точні назви й семантику звіряйте з документацією конкретного API. Існує активна IETF-чернетка (draft-ietf-httpapi-ratelimit-headers), що стандартизує поля RateLimit і RateLimit-Policy без префікса X-, але вона поки не набула статусу RFC.

    Для AQA rate limiting — часта прихована причина флаку, особливо в паралельних прогонах, коли десятки тестів гатять в API з одного токена. Практичні прийоми: поважати Retry-After (робити паузу саме на вказаний час, а не фіксовану), додавати експоненційну затримку з повторами (backoff) на транспортному рівні клієнта, розводити навантаження між тестовими акаунтами/ключами. І окремо: сам сценарій rate limiting теж треба тестувати — навмисно перевищити ліміт і переконатися, що приходить саме 429 (а якщо API обіцяє Retry-After — то з коректним значенням), а не 500 чи мовчазне зависання.

    GraphQL проти REST (оглядово)

    GraphQL — альтернативний підхід до API, який Facebook почав розробляти в 2012 році й відкрив публічно у 2015-му. Головні відмінності від REST:

    • Один ендпоінт (зазвичай POST /graphql) замість багатьох URL-ресурсів.
    • Клієнт описує, які саме поля йому потрібні, у мові запитів. Сервер повертає рівно їх — ні більше, ні менше. Це вирішує дві проблеми REST: over-fetching (прийшло більше даних, ніж треба) і under-fetching (довелося робити кілька запитів, щоб зібрати картину).
    query {
      user(id: 42) {
        name
        posts(first: 5) { title }
      }
    }

    Наслідки для тестування:

    • У GraphQL більшість відповідей приходить зі статусом 200, навіть коли є помилки — вони лежать у полі errors тіла. Тому перевірка «лише статусу» тут ще небезпечніша, ніж у REST: обовʼязково інспектуйте data та errors у тілі.
    • Форма відповіді визначається запитом, а не сервером, тож ваш асерт «знає» очікувану структуру заздалегідь.
    • Кешування й версіонування працюють інакше, ніж у REST: GraphQL зазвичай еволюціонує схему без явних версій — через застарілі (deprecated) поля.

    REST не «гірший» за GraphQL — вони розвʼязують різні задачі. Ваша робота як тестувальника — розуміти, з яким стилем маєте справу, бо від цього залежить, де шукати помилки у відповіді.

    API-контракт і contract testing

    Контракт (contract) — це формальна домовленість про те, як виглядає API: які ендпоінти існують, які приймають параметри й тіла, що і в якому форматі повертають, які статуси можливі. Часто контракт записують машиночитано — для REST це найчастіше OpenAPI (раніше відомий як Swagger), для GraphQL — схема (schema), для тіл — JSON Schema.

    Contract testing (контрактне тестування) перевіряє, що реалізація відповідає цьому контракту: чи не змінилася структура відповіді, чи не зникло поле, чи не змінився його тип. Розрізняють два напрями:

    • Валідація за схемою — прогнати відповідь через JSON Schema/OpenAPI й переконатися, що вона відповідає опису.
    • Consumer-driven contracts — споживач API описує свої очікування, а постачальник тестує, що їх не порушує (класичний інструмент — Pact).

    Чому це критично: найпідступніші поломки — не «сервіс упав» (це видно одразу), а «поле created_at тепер приходить як число замість рядка» або «status тепер може бути null». UI чи інший сервіс мовчки ламається на проді. Контрактний тест ловить такі зміни на межі сервісів ще до релізу. Для AQA це часто найдешевша й найстабільніша частина API-покриття: схема не залежить від конкретних значень, тож не флакає й не потребує підготовки складного стану.

    Версіонування API

    API живе й змінюється, але старі клієнти мають продовжувати працювати. Тому вводять версії. Поширені підходи:

    ПідхідПрикладКоментар
    У шляху URL/api/v1/users, /api/v2/usersнайпоширеніший, найпомітніший
    Через заголовокAccept: application/vnd.example.v2+json«чистіші» URL, складніше для клієнта
    Через query-параметр/api/users?version=2простий, але засмічує параметри

    Незалежно від підходу, ключове правило сумісності: у межах однієї версії допустимо додавати поля, але прибирати чи змінювати тип наявних — ламальна зміна (breaking change), яка вимагає нової версії. Звідси прямий висновок для написання асертів: перевіряйте наявність і тип потрібних вам полів, але не падайте від появи нових, невідомих полів у відповіді. Тест, який жорстко звіряє повний обʼєкт «поле в поле», зламається від безпечного розширення API — а мав би пропустити його. Перевіряйте те, що вам справді потрібно (підмножину), а не рівність усього тіла.

    API-тестування проти UI-тестування

    Це не «або-або», а два рівні піраміди тестування з різними сильними сторонами.

    КритерійAPI-тестиUI-тести
    Швидкістьвисока (мілісекунди — десятки мс)низька (секунди на дію)
    Стабільністьвисока, менше флакучутливі до верстки, таймінгів, локаторів
    Що покриваютьбізнес-логіку, контракти, даніреальний шлях користувача, візуал, інтеграцію фронта
    Підготовка станушвидка, прямими викликамиповільна, через клацання
    Чутливість до змін версткинечутливіламаються від зміни розмітки

    Практичний баланс: бізнес-правила, валідації, права доступу, крайові випадки покривайте на рівні API — там вони швидкі й надійні. UI-тестами перевіряйте те, що можна перевірити лише через інтерфейс: що дані справді відрендерилися, що сценарій користувача проходиться, що фронтенд коректно склеює виклики. І окремо — API часто найкращий інструмент для підготовки й прибирання стану в UI-тестах: створити користувача чи тестові дані одним POST замість проходу майстра з десяти екранів. Це і швидше, і стабільніше, і зменшує кількість місць, де тест може впасти не по суті.

    Ідемпотентність і стабільність тестів

    Повернемося до ідемпотентності, бо вона напряму годує надійність автотестів.

    Ідемпотентна операція дає той самий кінцевий стан незалежно від того, скільки разів її повторили. Це відрізняє її від однакового статус-коду: DELETE /users/42 двічі поспіль залишає систему в тому самому стані (користувача немає), хоча перший виклик може повернути 204, а другий — 404. Кінцевий стан ідемпотентний; проміжна відповідь — ні.

    Чому це рятує тести:

    • Стійкість до повторів (retry). Мережа моргнула, клієнт зробив повторну спробу — з ідемпотентним PUT/DELETE це безпечно й не створює дублів. З POST повтор може створити другий обʼєкт, і саме тут народжуються «привиди» в базі, що потім валять інші тести.
    • Ідемпотентні фази підготовки/прибирання. Setup, який спершу видаляє сутність, а потім створює, приводить систему до відомого стану незалежно від того, що там лишилося від попереднього прогону. Це основа ізоляції стану між тестами.
    • Захист від дублів у самому API. Багато платіжних і критичних API підтримують ключ ідемпотентності — клієнт передає унікальний токен, і сервер гарантує, що повтор із тим самим токеном не виконає операцію двічі.
    POST /api/v1/payments HTTP/1.1
    Content-Type: application/json
    Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7
    
    { "amount": 100, "currency": "USD" }

    Заголовок Idempotency-Key — це конвенція, популяризована окремими платіжними API, і предмет IETF-чернетки, а не універсальний HTTP-стандарт (на момент написання чернетка не набула статусу RFC). Тому його підтримку й точну назву звіряйте з документацією конкретного API.

    Звідси загальний принцип надійності: тест стабільний тоді, коли не залежить від невідомого початкового стану й не залишає по собі сміття. Ідемпотентні операції, самодостатні (stateless) запити, підготовка даних через API замість UI та асерти на інваріанти замість жорстких значень — ось інструментарій, який REST дає для боротьби з флаком.