Swagger and OpenAPI for Testers: Read Specs and Generate Tests

Swagger and OpenAPI for Testers: Read Specs and Generate Tests

On most projects the first thing you get for a new API is a link to its Swagger page. Testers who can read an OpenAPI specification properly start testing on day one, spot gaps in the contract before code is written, and generate collections, mocks and even test cases from it in minutes. This lesson teaches you to read and use OpenAPI as a tester, with a real specification for part of the SQA Tools practice API.

Quick answerOpenAPI (formerly called Swagger) is a standard YAML/JSON format that describes a REST API: its endpoints (paths), methods, parameters, request bodies, responses, schemas, and authentication (securitySchemes). Swagger UI renders it as interactive documentation where you can “Try it out”. Testers use the spec to design test cases, find missing or ambiguous rules, import requests into Postman, generate mocks and contract tests, and detect breaking changes between versions.
Advertisement

Swagger vs OpenAPI: Names Explained

TermWhat it is
OpenAPI Specification (OAS)The standard itself (versions 2.0, 3.0, 3.1). 2.0 was called “Swagger 2.0”.
Swagger UIInteractive HTML documentation generated from an OpenAPI file
Swagger EditorBrowser editor to write and validate specs
Swagger Codegen / OpenAPI GeneratorGenerate client SDKs and server stubs from the spec
Redoc, Stoplight, ScalarAlternative documentation renderers

So “send me the Swagger” usually means “send me the OpenAPI file or the Swagger UI link”. The official demo is the Petstore at petstore3.swagger.io; open it in a new tab to explore while you read.

Anatomy of an OpenAPI File

Here is an OpenAPI 3.0 description of three practice API endpoints. Read it top to bottom:

openapi: 3.0.3
info:
  title: SQA Tools Practice API
  version: 1.0.0
servers:
  - url: https://sqatools.in/wp-json/sqa-api/v1
paths:
  /users/{id}:
    get:
      summary: Get one user
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer, minimum: 1 }
      responses:
        '200':
          description: The user
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/User' }
        '400': { $ref: '#/components/responses/Error' }
        '404': { $ref: '#/components/responses/Error' }
  /objects:
    post:
      summary: Create a temporary object
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/NewObject' }
      responses:
        '201':
          description: Created
          headers:
            Location: { schema: { type: string, format: uri } }
            ETag:     { schema: { type: string } }
        '413': { $ref: '#/components/responses/Error' }
        '422': { $ref: '#/components/responses/Error' }
  /auth/me:
    get:
      security: [ { bearerAuth: [] } ]
      responses:
        '200': { description: Current user }
        '401': { $ref: '#/components/responses/Error' }
components:
  securitySchemes:
    bearerAuth: { type: http, scheme: bearer, bearerFormat: JWT }
  schemas:
    User:
      type: object
      required: [id, name, username, email, role, city, age, active, created_at]
      additionalProperties: false
      properties:
        id:         { type: integer, minimum: 1 }
        name:       { type: string }
        username:   { type: string }
        email:      { type: string, format: email }
        role:       { type: string, enum: [admin, editor, viewer] }
        city:       { type: string }
        age:        { type: integer, minimum: 18 }
        active:     { type: boolean }
        created_at: { type: string, format: date-time }
    NewObject:
      type: object
      required: [name]
      additionalProperties: false
      properties:
        name: { type: string, minLength: 1, maxLength: 100 }
        data: { type: object }
        ttl:  { type: integer, minimum: 10, maximum: 1800, default: 600 }
  responses:
    Error:
      description: Error
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: object
                required: [status, code, message]
Advertisement

Reading it as a tester

SectionWhat it tells youTest cases it gives you
serversBase URLs per environmentEnvironment variables in Postman/Playwright
paths + methodsEvery endpointCoverage checklist; 405 for undeclared methods
parametersPath/query/header params with types, required flags, rangesMissing, wrong type, boundaries (id 0, -1, abc)
requestBody schemaRequired fields, lengths, enums, defaultsBVA on ttl 9/10/1800/1801; name 0/1/100/101 chars; default ttl = 600
responsesEvery documented status code and schemaOne test per code; schema validation per response
headers in responsesLocation, ETag, rate limit headersHeader assertions
securityWhich endpoints need which auth401 without token for each secured operation
additionalProperties: falseNo extra fields allowedUnknown field → 422; responses don’t leak fields
Advertisement

Using Swagger UI to Test

  1. Open the Swagger UI link. Operations are grouped by tags.
  2. Click Authorize and paste your token or key once; it’s applied to secured calls.
  3. Expand an operation → Try it out → fill parameters/body → Execute.
  4. Swagger UI shows the cURL command, request URL, response code, body and headers.
  5. Compare the actual response with the documented one below it. Every mismatch is a bug in the API or in the docs.
Production Swagger UI “Try it out” against production can create real orders or send real emails. Use test environments. Also check whether Swagger UI should be publicly exposed at all in production (OWASP API9: inventory and exposure).
Advertisement

Finding Bugs in the Spec Itself (Shift-Left)

Reviewing the spec before development is one of the highest-value things a tester can do. Questions to ask:

  • Missing limits: name: { type: string } with no maxLength. What happens with 1 MB?
  • Missing error responses: only 200 documented. What about 400, 401, 404, 422, 429?
  • Ambiguous types: money as number (float) instead of integer paise or a decimal string.
  • Inconsistent naming: userId in one place, user_id in another.
  • Missing pagination on list endpoints that will grow.
  • Security gaps: sensitive endpoints without security.
  • Enum changes: are new values allowed later without breaking mobile apps?
Real-life example In a spec review for a refund API, a tester noticed amount had no maximum and no relation to the original payment. The team added “amount must not exceed captured amount minus previous refunds”. The would-be bug (refunding more than was paid) never reached code.
Advertisement

Spec vs Reality: Contract Testing

The spec is only useful if the API matches it. Compare them:

  • Schema validation of every response against the spec’s schemas (Ajv in Playwright, jsonSchema in Postman).
  • Undocumented endpoints: list what’s deployed and compare. The practice API exposes a discovery endpoint you can diff against docs:
GEThttps://sqatools.in/wp-json/sqa-api/v1/endpoints
curl -i \
  "https://sqatools.in/wp-json/sqa-api/v1/endpoints"
  • Undocumented status codes: the API returns 409 but the spec never mentions it.
  • Breaking changes between versions: tools like openapi-diff or oasdiff compare two spec files and flag removed endpoints, new required fields or changed types. Run them in CI on every pull request that touches the spec.
Advertisement

Generating Things from the Spec

GoalToolHow it helps testers
Postman collectionPostman → Import → OpenAPI file/URLAll requests created in seconds, with example bodies
SoapUI/ReadyAPI projectImport Swagger/OpenAPIResources and methods created automatically
Mock serverStoplight Prism (prism mock openapi.yaml), Postman mocksTest front-ends and write tests before the backend exists
TypeScript typesopenapi-typescriptTyped request/response objects in Playwright tests
Fuzz / property testsSchemathesisThousands of generated edge-case requests; any 5xx or schema mismatch fails
Security scanOWASP ZAP API scanAutomated checks on every documented endpoint
# Mock the API locally from the spec
npx @stoplight/prism-cli mock openapi.yaml          # serves on http://127.0.0.1:4010

# Fuzz the real (test) API from the spec
pip install schemathesis
schemathesis run openapi.yaml --url https://qa.example.com/api --checks all

# Detect breaking changes between two versions of the spec
oasdiff breaking old-openapi.yaml new-openapi.yaml

Using the Spec in Playwright Tests

import { test, expect } from '@playwright/test';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import fs from 'node:fs';
import YAML from 'yaml';

const spec = YAML.parse(fs.readFileSync('openapi.yaml', 'utf8'));
const ajv = new Ajv({ allErrors: true, strict: false });
addFormats(ajv);
const validateUser = ajv.compile(spec.components.schemas.User);
const BASE = spec.servers[0].url;

test('GET /users/{id} matches the OpenAPI User schema', async ({ request }) => {
  for (const id of [1, 2, 3]) {
    const res = await request.get(`${BASE}/users/${id}`);
    expect(res.status()).toBe(200);
    const ok = validateUser((await res.json()).data);
    expect(ok, JSON.stringify(validateUser.errors)).toBe(true);
  }
});

test('every documented error code for /users/{id} is reachable', async ({ request }) => {
  const documented = Object.keys(spec.paths['/users/{id}'].get.responses);   // ['200','400','404']
  const seen = new Set([
    (await request.get(`${BASE}/users/1`)).status(),
    (await request.get(`${BASE}/users/abc`)).status(),
    (await request.get(`${BASE}/users/999`)).status(),
  ].map(String));
  expect([...seen].sort()).toEqual(documented.sort());
});

The tests now fail automatically when the API drifts from its contract or the contract changes without the tests.

Advertisement

Writing Spec-Based Test Cases: A Quick Recipe

  1. For each operation, list every documented response code: one test each.
  2. For each parameter and body field: required/optional, type, format, min/max, enum → negative and boundary tests.
  3. For each security requirement: no token, invalid token, wrong role.
  4. For each response schema: schema validation on positive tests.
  5. For each response header: assertion.
  6. Then add what the spec can’t express: business rules, state transitions, cross-field rules, performance.

Common Mistakes

  • Trusting the spec blindly. It’s often out of date; the tests should prove it.
  • Only using Swagger UI for manual clicks and never importing the spec into tools.
  • Ignoring required and nullable differences (missing vs null).
  • Not versioning the spec in Git alongside the code.
Advertisement

Interview Questions: Swagger and OpenAPI

What is the difference between Swagger and OpenAPI?

OpenAPI is the specification standard for describing REST APIs. Swagger was its original name (Swagger 2.0) and is now the name of a set of tools such as Swagger UI, Swagger Editor and Swagger Codegen that work with OpenAPI files.

How do testers use Swagger/OpenAPI?

To understand endpoints, parameters, schemas and status codes; design test cases; try requests in Swagger UI; import collections into Postman or SoapUI; validate responses against schemas; generate mocks and fuzz tests; and detect breaking changes between versions.

What are the main sections of an OpenAPI file?

openapi version, info, servers, paths (operations with parameters, requestBody and responses), components (schemas, responses, parameters, securitySchemes) and security.

What is a $ref in OpenAPI?

A reference to a reusable definition, usually under components, such as $ref: ‘#/components/schemas/User’, so schemas and responses are defined once and reused.

How do you test that an API matches its OpenAPI specification?

Validate responses against the spec’s schemas in automated tests, check every documented status code and header, compare deployed endpoints with documented ones, and run tools like Schemathesis or contract tests in CI.

What is a breaking change in an API?

A change that can break existing clients: removing an endpoint or field, renaming a field, changing a type, adding a new required request field, or narrowing allowed values. Tools like oasdiff detect them by comparing specs.

How can you start testing before the backend is ready?

Use the OpenAPI spec to write test cases and automated tests, and run them against a mock server generated from the spec (Prism or Postman mocks). When the real API is deployed, point the same tests at it.

What would you look for when reviewing an API spec?

Missing validation limits, missing error responses, ambiguous types (especially money and dates), inconsistent naming, missing pagination, missing security on sensitive endpoints, and unclear enum and nullability rules.

What is the use of the “Try it out” button in Swagger UI?

It sends real requests to the API from the documentation page and shows the request URL, cURL command, status, headers and body, which is useful for quick exploration in test environments.

Should Swagger UI be exposed in production?

It depends on whether the API is public. For internal APIs it’s often disabled or protected in production, because it exposes the full attack surface. Testers should check it matches the company’s policy.

Advertisement
View the full API testing syllabus