Course outline

API Testing / Basic: trustworthy API observations / Lesson 2

Test HTTP responses as structured contracts

Combine method, status, headers, representation, and state effect into a coherent expectation.

About 4 min readContent reviewed
On this page

Lesson goals

  • Distinguish HTTP semantics from a service-specific business result.
  • Normalize header names while preserving values whose syntax matters.

Examine more than the status integer

An HTTP response is a combination of protocol information and representation. In the fictional catalog, a successful lookup returns a JSON representation with a declared media type and a stable book identifier. A status of 200 with an HTML sign-in page violates that contract even though the transport exchange succeeded. A creation response and a body-free response need different expectations. Keep protocol rules separate from application choices: HTTP defines method and status semantics, while the API decides its resource fields and business workflow. Read both sources before writing an assertion.

Normalize names at the correct layer

Header field names are case-insensitive. A test that recognizes Content-Type but rejects content-type is accidentally tied to a transport presentation detail. Normalize names before lookup, then parse values under their own grammar. Do not lowercase an entire response indiscriminately: an opaque identifier or token can be case-sensitive. Duplicate fields also need deliberate treatment because combining rules depend on the field. The small model uses a dictionary with one value per field and therefore cannot test repeated header behavior or wire-level parsing.

python Normalize field names without corrupting values
headers = {"Content-Type": "application/json; charset=utf-8", "X-Case": "Book-A"}
normalized = {name.lower(): value for name, value in headers.items()}
media_type = normalized["content-type"].split(";", 1)[0].strip().lower()
assert media_type == "application/json"
assert normalized["x-case"] == "Book-A"
print("header contract accepted")
Expected output
header contract accepted

This complete local example handles only a simple media-type value and unique dictionary keys. It demonstrates the chosen normalization boundary. A production parser must handle the documented field syntax and repeated values correctly.

Check when a representation is allowed

Assertions must follow the selected method and status. A HEAD response has no response content even though metadata can describe the corresponding GET representation. A 204 response also has no content. A generic helper that unconditionally parses JSON turns valid empty responses into decoder failures and hides the real contract. Branch at the response-contract layer: first establish whether content is expected, then verify its media type, then decode and validate it. The example is a synthetic decision check; it never creates, receives, or parses an actual HTTP message.

python A narrow response-content matrix
cases = [("GET", 200, b'{"id":"book-4"}'), ("HEAD", 200, b""), ("DELETE", 204, b"")]
for method, status, body in cases:
    content_forbidden = method == "HEAD" or status == 204
    if content_forbidden:
        assert body == b""
    else:
        assert body.startswith(b"{")
print("three response cases accepted")
Expected output
three response cases accepted

The first branch checks the no-content requirement for the listed examples. The other branch is only a fixture sanity check, not JSON validation. This intentionally small matrix is not an exhaustive implementation of all HTTP response rules.

Relate method meaning to state expectations

A safe method does not ask for a state change through its defined semantics. That does not prohibit incidental effects such as logging. Idempotency concerns the intended effect of repeated identical requests, not identical response bytes. Keep these distinctions in test descriptions. For a lookup, assert that protected catalog records remain unchanged; do not fail simply because an authorized audit counter changed. For a repeated deletion, choose expectations consistent with the documented resource contract. The second response may differ while the intended final resource state remains the same.

Localize protocol and business mismatches

When a response test fails, preserve method, path template, status, selected nonsecret headers, and a redacted structural summary of the body. Avoid treating every decoder error as a server business defect: the client may have followed a redirect or assumed the wrong content type. Compare the contract at each boundary before changing assertions. Keep exact assertions for stable fields and explicit tolerances for documented variation. Do not broadly accept every 2xx or every media type merely to remove a failure; that would weaken the claim the suite makes.

Common errors and fixes

JSON decoding fails on a valid 204

Why it happens
A shared helper assumes every success has content.
How to fix it
Decide content expectations before decoding.
Prevent it next time
Cover empty and body-bearing response contracts separately.

Header assertion depends on capitalization

Why it happens
The test compares field spelling rather than semantics.
How to fix it
Normalize field names and preserve meaningful values.
Prevent it next time
Use a field-aware response adapter with repeated-field tests.

Any 2xx response passes a creation test

Why it happens
A broad status range masks contract changes.
How to fix it
Assert the documented creation status, representation, and state effect.
Prevent it next time
Review status changes as contract changes.

Check your understanding

1. A HEAD check receives no body and the same Content-Type as GET. Is JSON parsing necessary?

Show worked solution

No. HEAD has no response content to decode. Check the relevant metadata and the absence of content according to the contract. Do not infer that the metadata is wrong merely because no JSON bytes arrive. Actual wire behavior still needs an authorized protocol-level test.

2. A repeated DELETE returns 204 and then 404. Does that alone disprove idempotency?

Show worked solution

No. Compare the intended final state: the resource remains absent after both requests. Response equality and idempotent effect are different properties. Whether these particular statuses satisfy the service contract is a separate assertion that must be documented.

Key takeaways

  • Assert a coherent status, header, representation, and state contract.
  • Parse only content that the method and status permit.
  • Normalize field names without rewriting opaque data.

Official references