API and Event Contracts Make Integration Testable

An integration becomes easier to operate when requests, events, retries, errors, and state transitions are explicit and independently testable.

Define the exchange, not only the endpoint

An API contract should tell a caller what it can send, what it may receive, and what each response means. That includes identifiers, validation behavior, pagination or limits where relevant, error shapes, and idempotency expectations for operations that might be retried. A URL and a JSON example are not enough when the result can trigger downstream work.

Events need the same discipline. An event name should carry a stable meaning, its payload should be versioned or evolvable, and consumers should know whether delivery is best effort, replayable, or merely a notification to fetch current state. These choices affect both user experience and recovery.

  • Name the resource, action, actor context, and expected state transition.
  • Provide machine-readable schemas where they reduce ambiguity.
  • Document retries, duplicates, ordering limits, and error handling.

Choose a transport for the interaction

Transport should follow the interaction pattern. A request-response API is often suitable when a client needs a direct result. Server-sent events provide a unidirectional stream from server to browser through the EventSource interface; a client must still have a separate path for sending commands. Full-duplex interaction has different requirements and should not be implied by an event stream.

The important design decision is not the protocol name. It is whether the caller can understand current state, recover after interruption, and avoid confusing a pending request with a completed effect. Interfaces should provide enough information to show that distinction.

  • Close streams when the user no longer needs live updates.
  • Give event consumers stable identifiers and explicit error states.
  • Avoid treating a delivery acknowledgement as business completion.

Test the contract at the boundary

Contract tests can verify that a provider and consumer agree on sample requests, responses, and error cases. They do not replace end-to-end validation, but they catch many accidental changes before a downstream team discovers them. Include realistic failures: missing fields, stale versions, duplicated events, timeouts, and partial results.

A mature handoff names the contract owner and the evidence available for the tested version. It does not guarantee that every external dependency is currently reachable or that a future version will behave identically.

Sources