Testing

API Contract Testing: Fewer Surprises Between Systems

Ing. Petra Hradecká, MBA · Editorial date: · ≈ 6 min

A small change in data structure can break an integrated process even when every team considers its own service healthy.

Reviewed and expanded on 28 October 2026. Examples describe documented past events; the proposed analysis is my own approach.

Illustration for API Contract Testing: Fewer Surprises Between Systems

This article develops my proposed approach to interface testing. It does not describe an incident from my own work. A documented public API change provides the starting case, followed by my analytical recommendations. I focus on the boundary between teams, where technical agreement must reflect what a customer actually needs.

Documented case: GitHub.com authentication changes

On 13 November 2020, GitHub announced that GitHub.com no longer accepted account passwords for REST API authentication and required tokens for authenticated operations. An earlier announcement described the timetable and migration steps. This is a documented change to an interface operated by a US company, rather than a claim that GitHub caused a particular publicly documented outage. Its testing lesson is that compatibility includes more than the JSON response. A consumer can parse every field correctly and still stop working when its authentication method is no longer supported.

Why this matters

Integration failures often appear late because providers and consumers are tested separately. Contract tests make the expectations at the boundary explicit: fields, types, required values, status codes and compatibility rules.

They do not replace integration testing, but they provide a fast signal when one side changes something the other side depends on.

Key idea: Treat the API contract as a shared, versioned agreement and test it from both provider and consumer perspectives.

How to do it well

01

Identify real consumers

List which services, jobs and external clients use each endpoint and which fields they rely on.

02

Capture expectations

Define request and response schemas, optionality, error behaviour and semantic rules.

03

Version the contract

Store OpenAPI, JSON Schema or consumer contracts alongside code and review changes.

04

Run consumer checks early

Verify that the consumer can create valid requests and interpret representative responses.

05

Verify the provider in CI

Confirm that the provider satisfies all supported consumer expectations before deployment.

06

Plan compatibility

Use deprecation periods, additive changes and clear migration paths for breaking changes.

What to avoid

  • Assuming that valid JSON means a compatible response.
  • Treating every field as required when consumers do not need it.
  • Changing enum values or meanings without considering semantic compatibility.
  • Maintaining contracts manually but never executing them.
  • Using mocks so unrealistic that integration risks remain hidden.

Hypothetical example

If an audience API changes an identifier from number to string, a provider unit test may still pass. A consumer contract immediately reveals that the downstream import expects numeric comparison.

An additive field is usually safe, while removing a field requires evidence that no supported consumer depends on it. The contract history makes that decision visible.

Lesson for practice

✓

Contract testing moves integration feedback closer to the change. Its value comes from explicit expectations, executable verification and disciplined compatibility—not from schema files alone.

A good practice does not have to be complicated. It should be intentional, repeatable and explainable: the team should understand why the control exists, what evidence it provides and how feedback will improve the next iteration.

What I would actually put in a contract

I would build the contract around the consumer’s business need. When submitting a contact, for example, acceptance of a request must be distinguished from completed processing. A response code alone may not establish that validation or storage has finished. My proposal is to describe the expected state after the response and the client’s next action. Every assertion must be verifiable against the provider. If the agreement promises only acceptance into a queue, the test must not imply that it verifies eventual delivery. That boundary identifies what another integration test should cover and what belongs to operational monitoring.

Alongside a successful response, I would choose errors that change user behaviour: invalid permissions, an exhausted limit, a missing object or partial rejection of a batch. This would not attempt to enumerate every technical possibility. Each scenario needs a reason, an owner and an expected consumer response. An authorization failure should not produce endless retries. Retrying temporary unavailability may be appropriate, but needs limits and protection against duplicates. These are proposed design considerations; the actual behaviour must follow the selected API’s documented semantics and product requirements. The team should be able to explain why it selected each failure case.

Technical compatibility and meaning

A change can pass a schema check while violating the agreement. An amount can remain numeric while switching currency or changing from a tax-inclusive to a tax-exclusive value. In my hypothetical design, units and meaning would be stated beside the assertion that depends on them. At the same time, I would avoid checking ordering or attributes that the consumer never uses. Excessive precision produces false alarms on harmless changes. Insufficient precision misses changes affecting a calculation. A productive contract discussion therefore begins with a concrete question: what would stop working for the user if this particular part of the response changed?

Follow the change through deployment

I propose retaining verification results together with the consumer version, provider version and contract configuration. An isolated green status is weak evidence. A team needs to know whether it verified the combination it intends to deploy. For an incompatible change, I would identify the last supported version and migration period in advance. When the provider is outside our organization, blocking its release may be impossible. Tracking announcements and checking our adapter in the environment the provider offers for that purpose become especially important. A test result needs enough context to support a release decision, including any relevant limitations of the verification environment.

The GitHub example usefully highlights this external dependency. I do not infer which internal tests the company used from its historical announcement. My consumer-side recommendation would be to locate integration paths, assign owners and verify readiness before support ends. A test relying on an old local mock can remain green after public conditions have changed. I would therefore periodically compare the contract with relevant documentation and observed provider behaviour. That review needs an explicit date rather than depending on one developer’s memory. Its result should explain which assumptions were checked and what remains outside the consumer’s direct control.

Evaluate usefulness rather than contract counts

The number of contracts is not a meaningful target by itself. I would track coverage of important consumers, incompatibilities detected before deployment and alarms resolved by simply rewriting expected values. During a repair, I want to know whether a legitimate product need changed or whether somebody merely adapted the test to the new response. In the latter situation, a green result may conceal the original problem. Review therefore needs someone who understands the consumer, alongside familiarity with the testing library. Unused integrations can be retired, but that decision should follow verification of actual use and ownership rather than an assumption based on missing recent changes.

Summary and recommendations

I would start with one important interface and a few decisive scenarios. Describe consumer expectations, verify them against the provider and associate results with specific versions. Authentication, permissions and data meaning deserve the same attention as field names. A migration needs an owner and deadline, while the complete business flow still requires additional testing. In this approach a contract is a maintained agreement that helps teams change systems with evidence about affected consumers. Its value comes from exposing consequential differences early enough for a deliberate response, rather than from making the pipeline appear more comprehensive.

Sources and further reading

← All articles