Course outline

DevOps QA / Basic: delivery evidence and suite design / Lesson 4

API behavior and compatibility contracts

Use API and contract evidence to detect incompatible changes before independently delivered components meet.

About 4 min readContent reviewed
On this page

Lesson goals

  • Separate schema conformance, consumer compatibility, and business correctness.
  • Version contract evidence with consumer and provider identities.

Define the compatibility claim

An API test exercises a concrete implementation through its request boundary. A schema describes allowed messages. A consumer-driven contract records interactions a consumer actually requires and verifies that a provider can satisfy them. These overlap but answer different questions. A response can satisfy a schema while carrying the wrong reservation count, and a provider can satisfy one consumer’s contract while breaking another. Name the consumer and provider versions, relevant provider states, and deployment combinations. Compatibility evidence is meaningful only when it matches the versions that may coexist during delivery.

Assert semantics as well as status

A successful HTTP status alone is weak evidence. Check the expected state transition, response fields used by the caller, and relevant durable postconditions. Negative cases should verify that invalid input produces the documented error without modifying protected state. Keep authentication and account ownership explicit. The example checks the zero-seat boundary through a synthetic local API. It deliberately avoids using a public endpoint. A real test fixture must provide its own isolated workshop and account, and a separate verification can ensure rejected requests did not consume capacity.

typescript A Playwright API case for the demo contract
import { test, expect } from "@playwright/test";

test("zero seats are rejected", async ({ request }) => {
  const response = await request.post("/api/reservations", {
    data: { workshopId: "owned-workshop", seats: 0 }
  });
  expect(response.status()).toBe(422);
  expect(await response.json()).toMatchObject({ code: "INVALID_SEAT_COUNT" });
});

Configure a local baseURL and test credentials through fixtures in a pinned Playwright project. The demo application must implement this status and error code; 422 is the example contract, not a universal rule for all APIs. No request is executed by this course.

Review message evolution deliberately

Removing a required response field or changing its type can break deployed consumers even if the provider’s own tests pass. Adding an optional field is often safer, but strict consumers and closed schemas can make additive changes incompatible too. Review actual parser behavior rather than relying on slogans. OpenAPI gives a versioned description language, while schema tooling checks only the rules it understands. The fragment below intentionally uses OpenAPI 3.1-compatible schema syntax. It is a component fragment, not a complete API document or proof of consumer compatibility.

yaml An OpenAPI 3.1 schema component
components:
  schemas:
    Reservation:
      type: object
      required: [id, seats, status]
      properties:
        id: { type: string, minLength: 1 }
        seats: { type: integer, minimum: 1 }
        status:
          type: string
          enum: [confirmed, cancelled]

Place under a complete OpenAPI document with paths and responses, then validate with tooling supporting the chosen specification version. The status enum is intentionally closed, so adding a status requires a compatibility review of consumers.

Verify published interactions against the provider

A consumer test against a mock proves how the client constructs and interprets messages under that mock’s rules. Provider verification connects those interactions to the implementation. Keep provider states deterministic and publish contracts only from identified revisions through a trusted workflow. A deployment decision must consider the consumers actually present or supported, not merely the newest contract file. Pact is one established implementation of this approach; its broker and verification workflow require explicit setup. Do not mistake a handwritten JSON response fixture for a verified consumer-driven contract.

Keep compatibility separate from functionality

A contract can prove that a provider returns fields and status a client expects without proving pricing, authorization, capacity enforcement, or performance. Keep those checks in provider functional suites and integration evidence. At a gate, require fresh verification for the candidate provider identity and relevant contract set. Preserve a failed interaction with redacted request and response details and the provider state used. When evidence is stale or missing, hold the compatibility claim open rather than reporting an empty contract selection as a successful verification.

Common errors and fixes

Provider tests pass but an old client breaks

Why it happens
Only the newest consumer contract was verified.
How to fix it
Verify the supported deployment combinations.
Prevent it next time
Track consumer versions and contract provenance.

Schema-valid responses carry incorrect business data

Why it happens
Shape checks replaced semantic assertions.
How to fix it
Add functional postconditions at the provider boundary.
Prevent it next time
List the limits of each contract claim.

A mock drifts from the real service

Why it happens
Fixtures are edited without provider verification.
How to fix it
Verify consumer interactions against the candidate provider.
Prevent it next time
Version and review contract artifacts like source.

Check your understanding

1. The provider adds a new status enum value. Why might this break a consumer despite being additive?

Show worked solution

A client may exhaustively switch over known values or validate against a closed enum. Check its actual parsing behavior and contract. Plan a compatible transition or consumer update, and verify the coexistence window rather than declaring every added field or value safe.

2. An API test asserts only response.ok(). Strengthen it for a rejected reservation.

Show worked solution

Assert the documented rejection status and machine-readable error, then verify capacity and reservations remain unchanged in owned fixture data. The case should fail if the server returns an attractive error body while still consuming a seat.

Key takeaways

  • Schema, compatibility, and business correctness need different evidence.
  • Contracts belong to identified consumer-provider combinations.
  • A mock is useful only within an explicit verification strategy.

Official references