⚠️ F5 · Contract Defects
F5 · Contract Defects: A contract defect returns to the "architectural blueprint" analogy from F1: the blueprint says "this room is 20 square meters" while it is really 15 — the
A contract defect returns to the "architectural blueprint" analogy from F1: the blueprint says "this room is 20 square meters" while it is really 15 — the building (the code) WORKS, the blueprint (the spec) EXISTS, but the two CONTRADICT each other. The auto-generation you saw in F2 REDUCES this risk but does not ZERO it out: even while generating a spec, a developer can write a wrong `@ApiResponse` annotation, or if the spec was hand-edited it may not have been updated when the code changed. So why is this an especially SNEAKY defect category — how does it differ from a normal functional bug? Because manual testing in the UI or a functional automation test usually ONLY asks "did the request succeed?", it does NOT ask "does the response EXACTLY match the DOCUMENTED shape?" — so a contract defect can live in production unnoticed for months, until a mobile app or a third-party integration trusting the spec crashes on a WRONG assumption. The Java equivalent is an `interface`'s JavaDoc falling out of sync with the code — the compiler does NOT catch this, because JavaDoc is not part of compilation; just as a spec being part of the "build" does not mean its CONTENT's accuracy is separately TESTED. For QA, contract testing is exactly the discipline that closes this gap.
4 Real Contract Defect Scenarios
The Doc Says... — The Real API Says...
**1. Status code mismatch** — the spec says `POST /api/v1/bugs` returns `200`, but the real API returns `201 Created`. **Root cause:** the developer moved to the correct practice on the code side (`201` = creation) but FORGOT to update the spec. **How the tester catches it:** running the real request with `Try it out` in Swagger UI and comparing the returned status code exactly against the doc.
**2. Enum drift** — the spec lists three `severity` values `[LOW, MEDIUM, HIGH]`, but the developer ADDED a new `CRITICAL` value on the code side and did NOT reflect it in the spec. **Root cause:** an enum can easily expand in a Java/TS constants list, but its counterpart in the spec is a SEPARATE list that must be updated by hand. **How the tester catches it:** seeing a value NOT in the doc in real responses, or expecting "CRITICAL should be rejected" in a negative test and noticing it is accepted instead.
**3. The required lie** — the spec lists `reporter` in the `required` list, but the real API also accepts a request sent WITHOUT this field and returns `201`. **Root cause:** the backend validation rule (`@NotBlank`/`@IsNotEmpty`) was either never written or is SILENTLY disabled somewhere (see the missing pipe/starter defects in B1/D3) — the spec is written correctly, but the code's REAL behavior differs. **How the tester catches it:** deliberately leaving every field marked "required" empty and getting 201 instead of 400.
**4. Field type mismatch** — the spec says `createdAt` is `type: string, format: date-time` (ISO-8601, e.g. `2026-07-24T10:00:00Z`), but the real API returns it as a UNIX timestamp number (`1753350000`). **Root cause:** a serialization setting changed on the backend (e.g. a different JSON library/config) but the spec did NOT catch this change. **How the tester catches it:** comparing a real response field's TYPE (string or number) exactly against the spec's `type`/`format` — a client (mobile app) that trusts the spec and auto-parses will CRASH on this mismatch.
🎬 The Contract Broke
Developer changed the code
Real API: returns 201
Mobile app trusts the doc
Tester catches the divergence
The doc was correct for months: it said `POST /api/v1/bugs` returns `200` on success, and it did.
A developer moves the code to "correct practice" (`201 Created`) — but FORGETS to update the spec.