Contract testing
1) Where to apply contracts
HTTP REST/JSON: resources, pagination, filters, idempotency, error codes.
gRPC/Protobuf: message types, statuses, 'deadline' semantics, backward-compat v.proto.
GraphQL: schemas, non-null, directives, permissive to fields.
Messages/streams (Kafka/Pulsar/SQS): event schemes (Avro/JSON/Protobuf), partition keys, order, idempotent keys.
Internal SDKs/Libraries: Public Features/Exceptions/Performance Contracts.
2) CDC Model: Roles and Artifacts
The consumer publishes the expectations contract (sample requests/responses, type matchers, invariants).
The supplier runs contract verification against its service/adapter/handlers.
The contract broker (Pact Broker/Backstage/repo artifact) stores versions, tags ('prod', 'staging', 'canary') and the 'consumer @ v → provider @ v' compatibility matrix.
Release policy: sending a provider is prohibited if any "prod-relevant" contract is violated.
3) What to fix in the contract (HTTP example)
Minimum:- Method/path/parameters/headers (incl. Auth, idempotent key).
- Body and typical matchers (type/format/regexp/ranges).
- Error codes and structure; stable'error _ code '.
- Semantic invariants: sorting, uniqueness, monotony 'created _ at'.
- Non-functional expectations (optional): p95, size limits, rate-limit headers.
json
{
"interaction": "GET /v1/users/{id}",
"request": { "method": "GET", "path": "/v1/users/123", "headers": {"Accept":"application/json"} },
"matchers": {
"response.body.id": "type:number",
"response.body.email": "regex:^.+@.+\\..+$",
"response.body.created_at": "format:rfc3339"
},
"response": {
"status": 200,
"headers": {"Content-Type":"application/json"},
"body": {"id": 123, "email": "alice@example.com", "created_at": "2025-10-31T12:00:00Z"}
},
"error_cases": [
{
"name":"not_found",
"request":{"path":"/v1/users/9999"},
"response":{"status":404, "body":{"error_code":"USER_NOT_FOUND"}}
}
]
}
4) Event-driven contracts
Event schema: 'type', 'version', 'id', 'occurred _ at _ utc', 'producer', 'subject', 'payload'.
Invariants: invariance 'id' and idempotence by '(type, id)', order within the key of the part, monotony 'sequence'.
Schema Registry-Stores evolution and compatibility rules (backward/forward/full).
Consumer contract tests: replay "golden" events and phases of negatives (unknown fields, nullable).
json
{
"type":"record","name":"UserRegistered","namespace":"events.v1",
"fields":[
{"name":"id","type":"string"},
{"name":"occurred_at_utc","type":{"type":"long","logicalType":"timestamp-millis"}},
{"name":"email","type":"string"},
{"name":"marketing_opt_in","type":["null","boolean"],"default":null}
]
}
5) Evolution and compatibility
Contract versions: 'MAJOR' semantics. MINOR. PATCH '(MAJOR - breaking).
Rules for REST:- Do not break: do not delete fields, do not change the type/value 'error _ code'.
- Add optional fields with default; new endpoints instead of "magic."
- Decrement: declaration, concurrent support, deletion by metrics.
- GraphQL: add-only fields, non-null enter through phases; decreeing directives.
- gRPC/Proto: do not reuse field numbers; only add new ones with optional.
- Events: scheme 'vN'; consumers are required to ignore unknown fields (leniency).
6) Negative and invariant checks
Negative: incorrect types, forbidden values, conflicting parameters, exceeding limits.
Invariants: sorting of responses, uniqueness of 'id', correctness of 'next _ cursor', stability of idempotent response when repeated.
Contracts of temporary aspects: 'created _ at' RFC3339/UTC, the correct projection of local days is not part of the transport contract - is submitted to business invariants.
7) Stab generation and local development
From contracts, provider stacks are generated for consumer development.
For events - generators of "valid/borderline" messages according to the scheme.
Staffs are marked with the contract version and build date; publication in prod.
8) Embedding in CI/CD (reference pipeline)
1. Consumer CI:
Lint/build → contract generation → unit/test contract → publication in contract-broker (tag: 'consumer @ 1. 7. 0`).
2. Provider CI:
Raising the service locally/in the container → fetch the relevant contracts ('prod '/' staging') → verification → publication of the status in the broker.
3. Release Gate:
The provider's deploy is blocked if there are outstanding contracts.
4. Nightly Matrix:
Compatibility matrix 'consumer versions × provider versions'; reports and alarms.
9) Examples of practices by domain
9. 1 REST: cursor pagination (contract invariant)
The response contains' items [] ',' next _ cursor '(nullable),' limit ',' total '(optional).
Invariants: 'len (items) ≤ limit', repeated call with the same 'cursor' → idempotent set.
Error if'cursor 'and'page' are both specified.
9. 2 POST idempotency
Contract requires'Idempotency-Key 'header.
Invariant: A repeated query with the same key returns the same 'id '/status.
9. 3 Events: guarantees of order
The partition key in the contract is' partition _ key = user_id'.
Invariant: 'sequence' increases monotonically within the key; the consumer must handle the replays.
10) Security and privacy in contracts
Do not include personal data/secrets in examples - only synthetics.
Fix mandatory security headers: 'Authorization', 'X-Signature', 'Replay-Prevention'.
For webhooks - signature and response contract '2xx '/retrays.
In the logs of contract tests - masking sensitive fields.
11) Tools
Pact/Pactflow/Pact Broker - HTTP/Message contracts, compatibility matrix.
OpenAPI/AsyncAPI - specifications + test generators (Dredd, Schemathesis).
Karate/REST Assured - scenario checks of REST contracts.
Protobuf/gRPC - 'buf', 'protolint', compatibility tests; Schema Registry for Avro/JSON/Proto in streams.
Conformance tests for GraphQL (graphql-compat), snapshot circuit tests.
12) Pseudocode of provider verification (simplified)
python def verify_contract(provider, contract):
for case in contract["cases"]:
req = build_request(case["request"])
res = provider.handle(req) # локально/контейнер assert match_status(res.status, case["response"]["status"])
assert match_headers(res.headers, case["response"].get("headers", {}))
assert match_body(res.body, case["matchers"], allow_extra_fields=True)
for neg in contract.get("error_cases", []):
res = provider.handle(build_request(neg["request"]))
assert res.status == neg["response"]["status"]
assert res.json.get("error_code") == neg["response"]["body"]["error_code"]
13) Anti-patterns
"Postman screenshots are a contract": no versions/typical matchers/automatic validation.
Oversnapping: the contract fixes exact values instead of types/patterns → false falls.
One common contract for different regions/channels: ignores variability (flags, geo-rules).
Contracts without broker/matrix: it is impossible to understand which versions are compatible.
Bet on e2e instead of contracts: slow, expensive, unstable.
No negative/invariant cases: only the "green track" is tested.
14) Observability and operation
Export status to broker + dashboard "health contracts."
Alerts: new drops in the provider against 'prod' contracts, an increase in "unknown field" in events.
Trace: 'contract _ id', 'version', 'decision _ id' in verification logs.
15) Depression process
1. Add a field/endpoint (non-breaking).
2. Mark the old as' deprecated'in the specification, announce the dates.
3. Track consumers by logs/broker; migration guides.
4. Enable "shadow" deny in the stage (dry-run), then enforce.
5. Delete after zero usage and compatibility.
16) Architect checklist
1. Consumers and their owners identified? Contracts are being versioned?
2. Is there a broker and a compatibility matrix with environment tags?
3. Are negatives and invariants (idiempotency, cursors, sorting) included in the contract?
4. Is Schema Registry and compatibility mode configured for events?
5. Pipeline blocks the release of the provider in case of violation of production contracts?
6. Is the deprection process and evolution policy described?
7. Stabs are generated from contracts, are there local event generators?
8. Is PD masking and mandatory safety headings documented?
9. Metrics/alerts on contracts are connected, are there drift reports?
10. Contracts are reviewed in CI by both parties (consumer and provider)?
Conclusion
Contract testing transfers "truth" about interactions into versioned artifacts and makes integrations predictable. CDC, contract broker, and scheme evolution discipline are replacing "breaking surprises" with a managed process: quick checks, clear invariants, and transparent version compatibility. This reduces the cost of e2e, speeds up releases and improves the quality of the entire platform.