SwadhyAI Notes

All System Design notes

System Design · APIs

API Testing Tools

A provider renames a JSON field, load doubles on launch day, and a dependency starts returning 500s with 2-second latency. Unit tests on the happy path catch none of those. API testing is the discipline—and the tool categories—that verify contracts, capacity, and failure behavior on the wire.

This note is conceptual: how , / / , and fit together. Tool names (Pact, k6, Gatling, Toxiproxy, and similar) are examples of the category, not a vendor bake-off. Pair with API compatibility notes for expand-contract versioning detail.

Three gates

1

Will old and new clients still agree on the wire?

2

Does the API meet SLOs at planned and sustained traffic?

3

When deps fail, do timeouts and fallbacks hold the SLO?

4

Do golden journeys stay green in production?

What actually happens

APIs are shared surfaces. Consumers deploy on their own clocks. A —especially —encodes what callers rely on so provider CI fails before a breaking deploy. and OpenAPI/Protobuf breaking-change checks are the schema-native cousins of the same idea.

Capacity is a different risk class. A correct contract at 50 QPS can miss an at 5,000 QPS. validate planned peaks; find the cliff and the failure mode; find leaks. Judge them by goodput and percentiles, not by whether the process stayed up.

Dependencies fail. uses on latency, errors, and partitions while the API is under representative load. You are testing client timeouts, retries, , circuit breakers, and fallbacks—not randomly killing boxes for sport. Continuous then watch the same journeys in production.

How to choose which gate

1. Changing a field or status? Contract + schema breaking checks before merge. Mobile and partner APIs need this every time.

2. Changing capacity or a hot path? Load to the new peak mix; soak if you touched pools, caches, or native memory.

3. Changing timeouts, retries, or a dependency? Fault-inject that dependency under load and assert SLO + graceful degradation.

Simple rule of thumb

Contracts for compatibility, load for capacity, chaos for resilience—each against an explicit SLO. Tool choice is secondary to the question and the abort criteria.

Contract and schema tooling (concepts)

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
sequenceDiagram
    autonumber
    participant C as Consumer CI
    participant Broker as Contract store
    participant P as Provider CI

    C->>Broker: Publish expectations (paths, fields, statuses)
    P->>Broker: Fetch contracts for this provider
    P->>P: Start provider (or stub of itself)
    P->>P: Verify each interaction
    alt Breaking change
        P-->>P: Fail build
    else Compatible
        P-->>P: Pass + deploy eligible
    end

In a flow, consumers generate interaction examples (request path, headers, body matchers, expected status and fields). A broker or repo stores them. Provider CI replays verification against a running provider (or a thin wrapper). Failures mean “this deploy would break that consumer,” which is a better signal than “our regenerated OpenAPI still looks tidy.”

tools compare OpenAPI/AsyncAPI/Protobuf across versions: removed fields, tightened required sets, and type changes become CI errors. Runtime validators at the gateway catch clients that send garbage and servers that drift from the published schema. Use to isolate consumers when the real provider is slow or costly—but periodically verify against a real staging provider so mocks do not lie.

plaintext
Contract checklist (provider PR):
  [ ] Consumer contracts green for all active consumers
  [ ] OpenAPI/Protobuf breaking-change detector green
  [ ] Additive-only claim verified (optional fields, new endpoints)
  [ ] Deprecation + Sunset plan if a break is intentional
  [ ] Example payloads in docs match verification fixtures

What contract tests do NOT replace:
  AuthZ matrix, money correctness, complex workflow state
  Those stay in focused integration / domain tests

Load, stress, and soak

Model a realistic traffic mix: read/write ratio, payload sizes, authenticated versus anonymous, cache hit rate. A flat GET flood that never touches checkout lies about capacity. Ramp gradually so you see the knee of the latency curve. Declare the before the run.

KindQuestionPass signal
LoadMeet SLO at expected peak?p95/p99 + error budget inside target
StressHow does it fail past peak?Graceful shed / 503, not cascade
SoakStable for hours at realistic load?Flat memory, pool, FD, disk curves
SpikeSudden 5–10× burst?Autoscale / queue / shed without meltdown

Tooling categories include scriptable HTTP load generators (k6, Gatling, Locust, Vegeta-class tools), protocol-specific generators for gRPC/WebSocket, and platform features (cloud load-test services). The design work is the scenario and metrics pipeline: export histograms, correlate with saturation, and store results next to the build that produced them.

plaintext
Minimal load-test design:
  Arrival: open model (users arrive) vs closed (fixed VUs)
  Mix: 70% GET /catalog, 20% GET /item, 10% POST /cart
  Peak: 1.5× forecast Black Friday QPS
  SLO: p99 < 300 ms, 5xx < 0.1%, goodput ≥ 99% of accepted
  Abort: error budget burn > X or DB CPU > 90% for 2 min

Always include:
  Think time that matches product (or document why not)
  Auth token acquisition (it is often the hidden bottleneck)
  Idempotent POSTs when replaying writes

Chaos and fault injection for APIs

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
flowchart TD
    Plan["Define journey + SLO"] --> Mix[Traffic mix / scenarios]
    Mix --> Load["Load / stress / soak"]
    Load --> Observe[Latency, errors, saturation]
    Observe --> Judge{SLO held?}
    Judge -->|No| Fix[Fix capacity or shedding]
    Fix --> Load
    Judge -->|Yes| Chaos["Fault injection on deps"]
    Chaos --> Guard{Guardrail / SLO}
    Guard -->|Abort| Learn[Record gap; harden]
    Guard -->|Hold| Promo[Promote confidence]
    Learn --> Fix

Useful API faults: dependency +500 ms / +2 s latency, 10–50% error rate, TCP reset, DNS failure, partial JSON, slow TTFB, disk full on the provider. Proxies and sidecars (Toxiproxy-class, service-mesh fault injection, chaos platforms) apply them without rewriting app code. Keep scoped: one dependency, one percentage of traffic, pre-agreed abort on error-budget burn.

Assertions should match product behavior: deadline propagation, bounded retries with jitter, fallback payloads, and under duplicate delivery. A “pass” is not “process still running”—it is “SLO and user journey still hold, or we degrade in the documented way.”

Good chaos hypothesis

“If payments returns 500 for 30s, checkout still answers within 800 ms with a retryable error body, and we do not double-charge when payments recovers.”

Bad chaos hypothesis

“Kill random pods in prod on Friday and see what happens.” No steady state, no blast radius, no abort, no learning captured.

Synthetics, environments, and flaky CI

run golden journeys continuously from outside. They catch cert expiry, bad deploys, and regional DNS issues that contract CI never sees. Keep them few, critical, and alert-quality— a noisy synthetic train teaches people to ignore pages.

Prefer contract and unit-speed checks on every PR; run heavier load in nightly or pre-prod pipelines; run chaos in staging or tightly guarded production experiments. Shared staging with noisy neighbors makes load results lie—isolate capacity tests or accept qualitative-only conclusions.

plaintext
Layering (fast → slow):
  PR:   schema break detect + consumer contracts + unit
  Nightly: load smoke at 30% peak
  Pre-release: full peak mix + soak 2–4 h
  Staging/prod experiment: dependency fault injection
  Always-on: 3–5 synthetic journeys per region

Flake control:
  Deterministic clocks and seeds where possible
  No shared mutable fixtures across parallel consumers
  Retry only infrastructure flakes, not assertion failures

Worked example: checkout API release

plaintext
Change: add optional field payment_method_v2; keep v1
  Gate 1 — Contract:
    Mobile + web consumer contracts still green
    OpenAPI diff: additive only; required set unchanged

  Gate 2 — Load (pre-prod):
    Mix includes 10% POST /checkout with new field
    Peak 1.2× forecast; p99 < 400 ms; 5xx < 0.1%
    Pool and DB CPU headroom > 30%

  Gate 3 — Chaos:
    Inject +1 s latency + 20% 500s on payments for 5 min
    Expect: bounded retries, no double charge (idempotency key),
            client-visible 503/409 within deadline
    Abort if checkout 5xx > 2% for 1 min

  Gate 4 — Synthetics:
    Prod probe posts sandbox checkout every 1 min
    Alert on 2 consecutive failures

Trade-offs to weigh

Contract coverage versus CI time

More consumers and interactions catch more breaks and slow every provider PR. Prioritize high-traffic and hard-to-upgrade clients (mobile, partners); do not contract-test every internal batch job on day one.

Mocks versus real providers

Doubles keep CI fast and isolated; they drift from production behavior. Schedule periodic contract verification against staging and keep wire-level matchers honest.

Prod-like load versus cost and risk

Full-scale tests are expensive and dangerous in shared environments. Scale a representative slice, validate linearity assumptions, and reserve full-peak runs for milestones.

Chaos learning versus blast radius

Wider experiments teach more and can hurt customers. Start in staging or a tiny traffic percentage with hard abort criteria; expand only after passes.

Tool sprawl versus one platform

One mega-tool rarely fits contracts, load, and chaos equally well. Prefer clear category ownership and shared SLO definitions over forcing every team onto one binary.

Choosing and explaining API testing (contract, load, chaos)

A complete explanation connects the mechanism to a requirement. Describe what improves when you introduce it, which new failure cases become possible, and how you would know that the trade-off is worthwhile.

Explain the core idea

API testing verifies wire compatibility, capacity against SLOs, and behavior under dependency failure—not only unit happy paths.

Consumer-driven contracts and schema break detectors gate compatible deploys; load/stress/soak gate capacity; fault injection gates resilience.

Always state the SLO and abort criteria; tool brands are secondary to the question each gate answers.

When it belongs

You design release gates for a public or mobile-facing API.

You must explain how to prevent breaking changes, launch-day collapse, or brittle dependency handling.

Interviewers ask how you would validate an API beyond Postman demos.

How it fails

Contracts that only mirror the provider’s own OpenAPI and never a real consumer.

Load tests with unrealistic mixes that miss the true bottleneck.

Chaos without blast radius, guardrails, or idempotency checks under retry.

Trade-offs to weigh

Broader contract coverage versus slower provider CI.

Higher-fidelity load environments versus cost and isolation.

Aggressive chaos learning versus customer blast radius.

Common misunderstandings

Treating regenerated docs as proof of compatibility.

Equating “server did not crash” with a passed load test.

Running chaos as entertainment without hypotheses or SLOs.

Wrap-up

Think in gates: contract for agreement, load for capacity, chaos for failure behavior, synthetics for ongoing truth. Choose tools as instances of those categories, bind every run to an SLO, and keep expand-contract versioning as the migration partner when a break is intentional.

Key takeaway

API testing tools are how you prove compatibility, capacity, and resilience on the wire. Pick the gate that matches the risk, measure against an SLO, and never confuse a green unit suite with a launch-ready API.