Lesson 24 / 27

Testing the API and the Contract

Combine unit, integration, contract and breaking-change tests.

Layers of confidence

Test an API at several levels. Unit tests cover business logic. Integration tests call the real HTTP endpoints against a test database and check status codes, headers and bodies, including error cases and the auth/ownership rules. Contract tests verify that responses match the OpenAPI schema (and, with consumer-driven contracts such as Pact, that the API still satisfies what real consumers expect). A breaking-change check in CI diffs the new spec against the released one (tools like oasdiff do this) and fails the pull request on incompatible changes unless a new version is intended. Add tests for pagination edges (empty, last page), idempotent retries, rate-limit responses and webhook signature verification. Run a smoke test against production after each deploy.

API tests in pytest (illustrative)

These check the behaviours demonstrated earlier in the course against a test client. client is your framework's test client fixture.

def test_idempotent_payment(client):
    h = {"Idempotency-Key": "k-1"}
    first = client.post("/v1/payments", json={"amount": 500}, headers=h)
    again = client.post("/v1/payments", json={"amount": 500}, headers=h)
    assert first.status_code == 201 and again.status_code == 200
    assert first.json() == again.json()
    assert again.headers["Idempotent-Replayed"] == "true"

def test_other_users_order_is_404(client, token_a, order_of_b):
    r = client.get(f"/v2/orders/{order_of_b}", headers={"Authorization": f"Bearer {token_a}"})
    assert r.status_code == 404

def test_last_page_has_null_cursor(client):
    assert client.get("/v1/items?limit=100").json()["next_cursor"] is None

Pin versions in tests

Keep a test suite per supported version. When you retire a version, delete its tests with the code, not before.

Quick check: What does a CI breaking-change check do?

  • Deletes old versions
  • Deploys to production
  • Compares the new spec with the released one and flags incompatible changes
  • Encrypts the spec
Answer

Compares the new spec with the released one and flags incompatible changes — Automatic diffing stops accidental breaking changes from reaching users.