BVA and Equivalence Partitioning for API Testing: Request Parameters, Headers, and Payloads

BVA and Equivalence Partitioning for API Testing: Request Parameters, Headers, and Payloads

API testing benefits from the same systematic input analysis as any other testing, but the input space is more structured: path parameters, query parameters, headers, and request bodies — each with their own validation rules and boundary conditions.

This post applies BVA and equivalence partitioning specifically to REST API inputs.

The API Input Taxonomy

Before partitioning, classify API inputs:

Path parameters (required, typed):

GET /users/{userId}
GET /orders/{orderId}/items/{itemId}

Query parameters (optional, typed, may have ranges):

GET /products?page=1&limit=20&min_price=10&max_price=100

Request headers (typed, often with restricted value sets):

Authorization: Bearer <token>
Content-Type: application/json
Accept-Language: en-US

Request body (structured, may have nested objects with their own validation):

{
  "name": "John Doe",
  "age": 25,
  "preferences": ["email", "sms"]
}

File uploads (size, type, content constraints)

Each category has different partition structures.

Path Parameters

Path parameters are typically IDs or resource identifiers. Common partition structure:

Numeric ID partitions

For GET /users/{userId}:

Partition Representative Expected
Valid existing ID 12345 200 OK
Valid non-existing ID 99999999 404 Not Found
ID = 0 0 400 or 404
Negative ID -1 400 Bad Request
Max int32 2147483647 400 or 404
Max int64 overflow 9223372036854775808 400 Bad Request
Non-numeric "abc" 400 Bad Request
SQL injection "1; DROP TABLE users" 400 Bad Request (no SQL execution)
UUID format (if wrong type) "550e8400-e29b-41d4-a716-446655440000" 400 Bad Request

The overflow value (max int64 + 1) tests whether the API handles large numeric strings without crashing. Many APIs crash or produce wrong results when a numeric ID exceeds the integer type used internally.

UUID partitions

For GET /resources/{uuid}:

Partition Representative Expected
Valid UUID (existing) "550e8400-e29b-41d4-a716-446655440000" 200 OK
Valid UUID (non-existing) "00000000-0000-0000-0000-000000000000" 404 Not Found
UUID format violation "not-a-uuid" 400 Bad Request
UUID with wrong version "550e8400-e29b-11d4-a716-446655440000" May accept or reject
Empty string "" 404 or 400
Nil UUID "00000000-0000-0000-0000-000000000000" 404 (or special case?)

The nil UUID partition is interesting: some systems treat it specially, some don't. Testing it reveals the system's behavior for this edge case.

Query Parameters

Query parameters are often optional, which adds a "missing" partition in addition to the usual value partitions.

Pagination partitions

For GET /products?page={page}&limit={limit}:

Page parameter (typically 1-indexed, must be positive):

Partition Value Expected
Missing (not provided) Default behavior (page 1)
Page 1 1 First page
Last valid page (depends on total count) Last page of results
Page beyond last N+1 Empty results or 404
Page 0 0 400 Bad Request
Negative page -1 400 Bad Request
Non-integer "first" 400 Bad Request
Very large page 9999999 Empty results or 400

Limit parameter (typically 1–100):

Partition Value Expected
Missing (not provided) Default limit
Minimum valid 1 Returns 1 item
Maximum valid 100 Returns up to 100 items
Below minimum 0 400 Bad Request
Above maximum 101 400 or capped at 100
Non-integer "all" 400 Bad Request
Very large 1000000 400 or OOM risk

The interaction between page and limit matters: page=100, limit=100 with a 50-item dataset should return empty results, not an error. Testing "page beyond last" requires knowing the total count — use a small dataset in tests to control this.

Filter parameter partitions

For GET /orders?status={status}&from_date={date}&to_date={date}:

Status filter (enum: PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED):

Partition Representative Expected
Valid status "SHIPPED" Filtered results
Unknown status "REFUNDED" 400 or empty
Case variant "shipped" 400 or case-insensitive match
Multiple values "PENDING,SHIPPED" Depends on API spec
Empty "" 400 or all statuses
Missing (not provided) All statuses

Date range (from_date / to_date):

Partition Representative Expected
from_date only from_date=2023-01-01 Orders from date to now
to_date only to_date=2023-12-31 Orders up to date
Valid range from=2023-01-01, to=2023-12-31 Orders in range
Inverted range from=2023-12-31, to=2023-01-01 400 Bad Request
Same day from=to=2023-06-15 Orders on that day
Invalid date from_date="yesterday" 400 Bad Request
Far future to_date=2099-12-31 All current orders

The inverted range partition (from > to) is commonly missed. Many APIs silently return empty results instead of an error.

Request Headers

Headers have value-type partitions and also have the "missing required header" partition.

Authorization header partitions

For Bearer token authentication:

Partition Value Expected
Valid token "Bearer " 200 OK
Missing header (not provided) 401 Unauthorized
Wrong scheme "Basic " 401 Unauthorized
Expired token "Bearer " 401 Unauthorized
Malformed JWT "Bearer not.a.jwt" 401 Unauthorized
Token for different service "Bearer " 401 Unauthorized
Token with wrong permissions "Bearer " 403 Forbidden
Extremely long token "Bearer " + "A" × 10000 400 or 431

The "wrong permissions" partition distinguishes 401 (unauthenticated) from 403 (authenticated but unauthorized). Many APIs return 403 for everything or 401 for everything — testing this partition verifies correct HTTP semantics.

Content-Type header partitions

For POST/PUT endpoints:

Partition Value Expected
Correct type "application/json" 200 OK
Missing (not provided) 400 or 415
Wrong type "text/plain" 415 Unsupported Media Type
With charset "application/json; charset=utf-8" Should work
Unknown type "application/x-custom" 415

Request Body Partitions

Complex request bodies need partitioning at multiple levels.

Structural partitions (JSON body)

For any JSON request body:

Partition Representative Expected
Valid JSON {"key": "value"} 200
Invalid JSON "{key: value}" 400
Empty body (no body) 400 or treats as empty object
Empty JSON object {} 400 (missing required fields)
Extra fields Valid + {"unknown_field": "x"} 200 (ignore) or 400
Null body null 400
Array instead of object [{"key": "value"}] 400
Very large body Valid payload × 1000 413 or 400

Field-level partitions

For each field in the request body, apply the relevant EP rules based on type (string, numeric, date, enum, collection — as covered in the previous sections).

Additionally for required/optional:

Partition Description Expected
All required fields Complete valid body 200
Missing required field Body with one required field omitted 400
Null required field Required field set to null 400
All optional fields omitted Only required fields 200 (defaults apply)
Optional field set to null Explicit null for optional 200 (treated as absent) or 400

Testing every combination of missing required fields is O(2^n) — impractical. At minimum, test: each required field missing independently, and all required fields present.

Nested object partitions

For nested objects, apply partitions recursively:

{
    "user": {           // Required object
        "name": "...",  // Required field
        "email": "...", // Required field
        "address": {    // Optional nested object
            "street": "...", // Required if address present
            "city": "..."    // Required if address present
        }
    }
}

Partitions for the address object:

  • Address absent (valid — optional)
  • Address present, fully specified (valid)
  • Address present, street missing (invalid — required if address present)
  • Address present as empty object {} (invalid — required fields missing)
  • Address present as null (treat as absent, or error?)

Rate Limiting Boundaries

APIs with rate limiting have boundary conditions at the rate limit itself:

Partition Requests Expected
Well under limit 1 request 200
At limit - 1 Limit - 1 requests 200
At limit Limit requests 200
Just over limit Limit + 1 requests 429 Too Many Requests
Well over limit 10× limit requests 429
After window reset First request after window 200

Rate limiting tests require time control — you need to know when the rate limit window starts and resets. Testing this against a real API requires either a mock rate limiter or timing the requests to a specific window.

Check that 429 responses include:

  • Retry-After header
  • X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers
  • Clear error message

Idempotency Partitions

For idempotent operations (PUT, DELETE), test:

Partition Description Expected
First operation Normal execution 200 or 201
Duplicate operation (same payload) Same request twice 200 (second = no-op) or 204
Duplicate with changed payload Same ID, different data 409 or 200 (update)
Operation on already-deleted resource DELETE twice 204 or 404

Idempotency bugs are common in distributed systems: a network timeout causes a retry, and the system incorrectly processes the same operation twice.

Documenting API Partitions

For each API endpoint, document the partition structure in a test plan:

Endpoint: POST /orders
Parameter: body.quantity (int)

Partitions:
- EP1 (invalid): quantity < 1 → 400 (test with: 0, -1)
- EP2 (valid): 1 ≤ quantity ≤ 100 → 201 (test with: 1, 50, 100)
- EP3 (invalid): quantity > 100 → 400 (test with: 101, 1000)
- EP4 (invalid): non-integer → 400 (test with: "five", 1.5)
- EP5 (invalid): missing → 400 (test with: field absent)

This documentation makes partition coverage explicit and reviewable. It's also the specification for automated test generation.

Summary

BVA and EP for APIs follow the same principles as any input analysis: identify the behavioral regions, test one representative from each region, test the boundaries between regions.

The API-specific additions: path parameters need overflow and injection partitions; query parameters need "missing" partitions; headers need "wrong scheme" and "wrong permissions" partitions; request bodies need structural partitions in addition to field-level partitions.

The combination of systematic EP with BVA at the boundaries produces test suites that cover the full input space at reasonable cost — a fraction of the tests that exhaustive testing would require, while covering the failure modes that matter.

Read more

Start now free