QA Interview Hub · База знань · Тести

API

Документація й контракти: OpenAPI, JSON Schema, contract testing

Swagger/OpenAPI у тестуванні

OpenAPI — специфікація опису REST API, Swagger — набір інструментів навколо неї, зокрема інтерактивний Swagger UI. Специфікація описує endpoints, методи, параметри, схеми запитів і відповідей, коди відповідей та авторизацію. Для QA це головне джерело вимог до API.

Через Swagger UI можна швидко виконати запит прямо з браузера, авторизуватися й подивитися реальну відповідь. Специфікацію можна імпортувати в Postman, щоб автоматично отримати колекцію всіх запитів. Також з неї беру схеми для валідації відповідей в автотестах.

Під час тестування перевіряю, чи реальна поведінка API відповідає документації: типи полів, обов'язковість, коди помилок. Розбіжності — це або баг у коді, або баг у документації, і обидва варто заводити. На основі специфікації будують contract testing та генерують API-клієнти.

OpenAPI як джерело тестів

OpenAPI-специфікація описує ендпоінти, параметри, схеми тіл, коди відповідей і авторизацію. Для мене це не лише документація, а сировина для автотестів.

Як я її використовую на практиці:

  • Беру схеми з components/schemas і валідую ними відповіді в автотестах, замість того щоб переписувати структуру вручну.
  • Генерую TypeScript-типи зі специфікації (наприклад, openapi-typescript), і зміна контракту ламає компіляцію тестів.
  • Проганяю лінтер специфікації в CI, щоб ловити неповні описи: відсутні коди помилок, поля без типу.
  • Порівнюю специфікацію між версіями, щоб побачити зміни, що ламають клієнтів.

Типові розбіжності, які знаходжу: поле позначене обов'язковим, але приходить null; задокументовано 400, а реально 422; у специфікації немає опису помилок взагалі; enum у документації не збігається з реальними значеннями.

Transfer:
  type: object
  required: [id, status, amount]
  properties:
    id: { type: string }
    status: { type: string, enum: [pending, completed, failed] }
    amount: { type: number }
На співбесіді: OpenAPI для мене — джерело правди і сировина для тестів: зі схем я валідую відповіді й генерую типи. Будь-яку розбіжність між специфікацією і реальною поведінкою заводжу як баг — або в коді, або в документації.

Валідація схем у коді: ajv і zod

У Postman схему перевіряє jsonSchema, а в TypeScript-автотестах зазвичай використовують ajv (JSON Schema валідатор) або zod (схеми прямо в TypeScript з виведенням типів).

import { test, expect } from '@playwright/test';
import { z } from 'zod';

const Transfer = z.object({
  id: z.string(),
  status: z.enum(['pending', 'completed', 'failed']),
  amount: z.number().positive(),
  currency: z.string().regex(/^[A-Z]{3}$/),
}).strict();

test('create transfer matches contract', async ({ request }) => {
  const res = await request.post('/v1/transfers', {
    data: { toAccount: 'UA21', amount: 150, currency: 'UAH' },
  });
  expect(res.status()).toBe(201);
  const parsed = Transfer.safeParse(await res.json());
  expect(parsed.success, JSON.stringify(parsed.error?.issues)).toBe(true);
});

.strict() у zod працює як additionalProperties: false — зайве поле валить тест. Вибір інструмента простий: якщо схеми вже є в OpenAPI чи JSON Schema, беру ajv і не дублюю їх; якщо схем немає, zod зручніший, бо одночасно дає і перевірку, і тип.

На співбесіді: У коді я валідую відповіді через ajv, якщо є готові JSON-схеми з OpenAPI, або через zod, якщо описую контракт сам у TypeScript. Строгий режим ловить зайві поля, а повідомлення про помилку одразу показує, яке поле не збіглося.

Consumer-driven contracts і breaking changes

Contract testing перевіряє, що два сервіси розуміють одне одного, без підняття всієї системи. У підході consumer-driven contracts (інструмент Pact) споживач, наприклад фронтенд чи інший мікросервіс, описує в тесті, які запити він робить і які поля очікує. З цього генерується контракт, він публікується в Pact Broker, і провайдер у своєму CI перевіряє, що реальний API цей контракт виконує.

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

Breaking vs non-breaking changes:

ЗмінаТип
Додати нове необов'язкове поле у відповідьNon-breaking
Додати новий ендпоінтNon-breaking
Додати необов'язковий параметр у запитNon-breaking
Видалити або перейменувати поле відповідіBreaking
Змінити тип поля, наприклад число на рядокBreaking
Зробити необов'язкове поле запиту обов'язковимBreaking
Змінити статус-код чи формат помилкиBreaking

Нюанс: нове значення в enum формально «додавання», але може зламати клієнта, який обробляє лише відомі значення.

На співбесіді: У consumer-driven contracts споживач фіксує свої очікування в контракті через Pact, а провайдер перевіряє його у своєму CI до деплою. Додавання полів зазвичай не ламає клієнтів, а видалення, перейменування чи зміна типу — breaking change, яке потребує нової версії API.