Course outline

API Testing / Basic: trustworthy API observations / Lesson 3

Test JSON boundaries and representation choices

Distinguish parsing, structural validation, and domain validation with deliberately small boundary cases.

About 4 min readContent reviewed
On this page

Lesson goals

  • Separate missing, null, empty, false, and zero without implicit coercion.
  • Choose boundary cases from the documented field domain and encoding rules.

Name the validation layer

JSON parsing answers whether bytes can be interpreted as JSON. Structural validation asks whether the decoded value has the required object, array, and field shapes. Domain validation asks whether that shape represents an allowed business action. A syntactically valid number is not automatically a valid page size, and a well-formed date string does not prove that a reservation is permitted. Report these failures separately so a client can repair the right input. The classroom examples start with local strings and dictionaries; they do not test an HTTP body limit or a framework parser.

Keep absence distinct from explicit values

A partial update often uses field presence to express intent. Omitting a subtitle can mean leave it unchanged, while null can mean clear it only if the contract permits clearing. An empty string is still a supplied string. Avoid truthiness checks such as if value when zero, false, or an empty collection can be valid. The first program preserves the difference between absent, null, and ordinary values. It is intentionally a classifier, not a general request validator. The surrounding API must document which of those states each field accepts.

python Classify field presence explicitly
def classify(payload, key):
    if key not in payload:
        return "absent"
    if payload[key] is None:
        return "null"
    return "value"

assert classify({}, "subtitle") == "absent"
assert classify({"subtitle": None}, "subtitle") == "null"
assert classify({"subtitle": ""}, "subtitle") == "value"
print("presence cases accepted")
Expected output
presence cases accepted

The complete program compares three independent payloads without mutating them. It proves the classifier distinguishes the listed cases. It does not decide whether clearing or empty text is allowed in a real application.

Test just inside and outside a domain

For a documented page size from one through fifty, choose zero, one, fifty, and fifty-one before adding random examples. Also consider a string containing digits, a fractional value, and a boolean. Python treats bool as a subtype of int, so isinstance(value, int) alone would accept True. Other runtimes have different coercion rules. Define the API value domain rather than borrowing a language accident. The second example uses exact integer type for this synthetic policy and returns a stable error marker without exposing a raw exception.

python A bounded integer policy with explicit type handling
def page_size(value):
    if type(value) is not int or not 1 <= value <= 50:
        return "INVALID_PAGE_SIZE"
    return value

for value in [0, 51, "3", 1.5, True, None]:
    assert page_size(value) == "INVALID_PAGE_SIZE"
for value in [1, 50]:
    assert page_size(value) == value
print("eight boundary cases accepted")
Expected output
eight boundary cases accepted

This is one application policy, not a universal JSON Schema implementation. Eight local cases are checked. A real parser, overflow behavior, duplicate object names, and request-size enforcement require separately scoped tests.

Specify text and numeric representation

Length is ambiguous until the contract defines what is counted. Bytes, Unicode code points, and user-perceived characters differ, especially for combined marks and emoji. Currency and identifiers deserve equally explicit representation decisions: decimal fractions may require decimal arithmetic or minor-unit integers, and large identifiers may be safer as strings for heterogeneous clients. Tests should preserve the chosen representation across serialization and parsing. Use short synthetic multilingual fixtures and boundary-sized strings; there is no reason to submit enormous payloads to a real service to teach these distinctions.

Reject ambiguity before accepting a test result

Unknown properties, duplicate object names, non-finite numeric spellings, and date formats can be handled differently by parsers and schema tools. Pin the actual parser and schema dialect in an approved environment, then document the intended behavior. Do not assume that a format annotation is always enforced as an assertion by every JSON Schema validator. Preserve a distinction between malformed input and a valid value outside business policy. A passing helper test is useful evidence about the helper; only a separately approved boundary test establishes how the application receives and interprets bytes.

Common errors and fixes

Zero is rejected as missing

Why it happens
A truthiness condition conflates valid values and absence.
How to fix it
Check membership, nullability, type, and range separately.
Prevent it next time
Include false, zero, empty, null, and omitted cases in field reviews.

True passes an integer-only field

Why it happens
The validator inherits Python boolean subtype behavior.
How to fix it
Use the explicit value-domain rule intended by the contract.
Prevent it next time
Test runtime-specific coercion with synthetic cases.

An invalid date passes the schema check

Why it happens
The selected validator treats format as annotation.
How to fix it
Check dialect, vocabulary, and configured format enforcement.
Prevent it next time
Record validator behavior rather than assuming keyword enforcement.

Check your understanding

1. The field limit is fifty UTF-8 bytes. Is a fifty-character Arabic string necessarily valid?

Show worked solution

No. The byte limit is checked after encoding, and many Arabic characters occupy multiple UTF-8 bytes. Use a short reviewed fixture and compute its encoded length locally. Also test the documented boundary, then separately verify that the application and client agree on the same unit.

2. A partial update omits subtitle. Should a test expect it to become null?

Show worked solution

Only if the contract explicitly defines omission that way. Under the common fictional policy here, omission leaves it unchanged and explicit null clears it when permitted. Build distinct before/after cases for both intentions instead of using one empty-payload test for everything.

Key takeaways

  • Parsing, structural shape, and business validity answer different questions.
  • Boundary tests must include representation and coercion choices.
  • Missing and explicit null are distinct inputs.

Official references