Negative and Boundary Value Testing for APIs with Real Examples

Negative and Boundary Value Testing for APIs with Real Examples

Any developer can make an API work with perfect input. Your job as a tester is to find what happens with everything else: the empty field, the age of 17, the price of minus one, the 5 MB name, the emoji, the duplicated key. This is where most API bugs and many security issues live. In this lesson you’ll learn a systematic method for negative and boundary testing, and run more than 40 real cases against a live validation endpoint.

Quick answerNegative API testing sends invalid, missing, malformed or unexpected input and checks the API rejects it safely: the right 4xx status (400 for unparseable requests, 422 for rule violations, 415 for wrong content type, 413 for oversized bodies), a clear field-level error message, no data saved and never a 5xx. Boundary value analysis tests the edges of every limit: min-1, min, max and max+1 for numbers, lengths, sizes, dates and list counts.
Advertisement

Why Negative Testing Matters So Much

Real-life examples Negative quantity: an online store accepted "quantity": -1, so the line total became negative and the order total dropped below zero. Unvalidated amount: a wallet top-up API trusted an amount field sent from the client. 500 on emoji: a delivery app crashed when a customer named their address “Home 🏠”. Each one is a missing negative test.
  • Users make mistakes, old app versions send old formats, partner systems send surprises, and attackers send everything.
  • Correct rejection protects data integrity: a bad request must not half-save.
  • Clear error messages reduce support tickets and help front-end developers show the right field error.

The Validation Endpoint We’ll Test

POST /validate/user on the practice API validates a user payload without saving it. Its rules:

FieldRule
nameRequired string, 2 to 50 characters (spaces-only rejected)
emailRequired, valid email format
ageRequired whole number, 18 to 100 (not a string)
roleOptional: admin, editor or viewer (default viewer)
phoneOptional: 10-digit Indian mobile number starting with 6 to 9
Anything elseRejected as “Unknown field”
POSThttps://sqatools.in/wp-json/sqa-api/v1/validate/user
curl -i \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"name":"A","email":"asha@","age":"30","role":"manager","salary":1}' \
  "https://sqatools.in/wp-json/sqa-api/v1/validate/user"

One request, five errors, all reported together with the field name. That’s the behaviour to expect (and to ask for) from any API.

Advertisement

Technique 1: Boundary Value Analysis

Developers commonly write < where they meant <=. Boundaries catch those off-by-one bugs.

Fieldmin-1minmaxmax+1
age (18-100)17 → 42218 → 200100 → 200101 → 422
name length (2-50)1 char → 4222 chars → 20050 chars → 20051 chars → 422
ttl on /objects (10-1800)9 → 42210 → 2011800 → 2011801 → 422
per_page (1-50)0 → 4001 → 20050 → 20051 → 400
expires_in on login (5-3600)4 → 4225 → 2003600 → 2003601 → 422
Request body size (10 KB)––under 10 KB → okover 10 KB → 413

Other boundaries to look for in real APIs: dates (today, past, far future, Feb 29, time zones), list sizes (0 items, 1, max items in a cart), money (0, 0.01, max transaction limit), file sizes and counts (5th vs 6th saved address).

Technique 2: Equivalence Partitioning by Data Type

For each field, test one value from every “class” of wrong input:

ClassExample for agePractice API result
Valid30200
Number as string“30”422
Decimal30.5422
Negative-1422
Nullnull422
Missing key(no age)422
Huge number1e309422
Boolean / array / objecttrue, [30], {“v”:30}Should be 422

Do the same for strings (empty, spaces only, very long, Unicode, emoji, HTML, SQL), enums (wrong value, wrong case “Admin”), and formats (email without domain, phone with country code, date in the wrong format).

Advertisement

Technique 3: Structural (Request-Level) Negative Tests

These break the request itself, not one field:

CaseHowExpected
Malformed JSON{"name": "Asha", 400 invalid_json
Empty bodyNo body400 missing_body
Wrong top-level type[{"name":"Asha"}]400 invalid_body
Wrong Content-Typetext/plain415
Oversized bodyOver 10 KB413
Unknown field"salary": 1422 “Unknown field”
Duplicate keys{"age":30, "age":17}Consistent rule (practice API: last value wins → 422)
Wrong methodDELETE /products/101405 + Allow
Wrong URL / version/v9/users404, no stack trace
Unsupported AcceptAccept: text/csv406
POSThttps://sqatools.in/wp-json/sqa-api/v1/validate/user
curl -i \
  -X POST \
  -H "Content-Type: application/json" \
  -d '[{"name":"Asha"}]' \
  "https://sqatools.in/wp-json/sqa-api/v1/validate/user"
Duplicate keys: a real security issue If a gateway reads the first "amount" and the backend reads the last one, an attacker can get a request approved for Rs 1 and executed for Rs 10,000. Whatever the rule, all layers must agree. Ideally, duplicate keys are rejected.

Technique 4: Cross-Field and Business Rule Violations

  • min_price greater than max_price: the practice API returns 400 “min_price cannot be greater than max_price”.
  • End date before start date in a booking.
  • Coupon valid only above Rs 500 applied to a Rs 499 cart.
  • Delivery pincode outside the service area.
  • Body id different from the URL id in a PUT (practice API: 422).
  • Transfer amount above the account balance or the daily limit.
GEThttps://sqatools.in/wp-json/sqa-api/v1/products?min_price=5000&max_price=100
curl -i \
  "https://sqatools.in/wp-json/sqa-api/v1/products?min_price=5000&max_price=100"
Advertisement

Technique 5: Special Characters, Unicode and Injection Strings

InputWhyExpected
आशा वर्मा, Zoë, O'BrienReal namesAccepted and stored exactly
😀🏠4-byte UTF-8 (breaks some databases)Accepted or cleanly rejected; never 500
" "Whitespace onlyTreated as empty (practice API: 422)
" Asha "Leading/trailing spacesTrimmed or rejected consistently
' OR '1'='1SQL injectionStored as text or rejected; no SQL errors
<script>alert(1)</script>Stored XSSStored as text, escaped on display
\u0000, control charactersNull bytesRejected
10,000-character stringLength limits, buffer issues422/413, not 500

What a Good Error Response Looks Like

{
  "error": {
    "status": 422,
    "code": "validation_failed",
    "message": "2 field(s) failed validation.",
    "details": [
      { "field": "age",   "message": "age is required: a whole number from 18 to 100 (not a string)." },
      { "field": "phone", "message": "phone must be a 10-digit Indian mobile number starting with 6-9." }
    ]
  }
}

Check every negative response for:

  • The specific status code (not a generic 400 for everything, never 200, never 500).
  • A machine-readable code and the field name, so apps can highlight the right input.
  • All errors at once, not one per attempt.
  • A human-readable message without internals (no SQL, stack traces or class names).
  • The same error format on every endpoint.
  • No side effects: nothing created, updated, charged or emailed.
Advertisement

Automating 40+ Negative Cases with Playwright

import { test, expect } from '@playwright/test';

const URL = 'https://sqatools.in/wp-json/sqa-api/v1/validate/user';
const valid = { name: 'Asha', email: 'asha@example.com', age: 30 };

type Case = { title: string; body: Record<string, unknown>; status: number; field?: string };

const cases: Case[] = [
  // Boundaries
  { title: 'age 17',  body: { ...valid, age: 17 },  status: 422, field: 'age' },
  { title: 'age 18',  body: { ...valid, age: 18 },  status: 200 },
  { title: 'age 100', body: { ...valid, age: 100 }, status: 200 },
  { title: 'age 101', body: { ...valid, age: 101 }, status: 422, field: 'age' },
  { title: 'name 1 char',   body: { ...valid, name: 'A' },            status: 422, field: 'name' },
  { title: 'name 2 chars',  body: { ...valid, name: 'As' },           status: 200 },
  { title: 'name 50 chars', body: { ...valid, name: 'a'.repeat(50) }, status: 200 },
  { title: 'name 51 chars', body: { ...valid, name: 'a'.repeat(51) }, status: 422, field: 'name' },
  // Types
  { title: 'age as string', body: { ...valid, age: '30' }, status: 422, field: 'age' },
  { title: 'age decimal',   body: { ...valid, age: 30.5 }, status: 422, field: 'age' },
  { title: 'age null',      body: { ...valid, age: null }, status: 422, field: 'age' },
  { title: 'name spaces',   body: { ...valid, name: '   ' }, status: 422, field: 'name' },
  // Formats and enums
  { title: 'email no domain',  body: { ...valid, email: 'asha@' },          status: 422, field: 'email' },
  { title: 'unknown role',     body: { ...valid, role: 'manager' },        status: 422, field: 'role' },
  { title: 'phone with +91',   body: { ...valid, phone: '+919876543210' }, status: 422, field: 'phone' },
  { title: 'valid phone',      body: { ...valid, phone: '9876543210' },    status: 200 },
  { title: 'unknown field',    body: { ...valid, salary: 1 },              status: 422, field: 'salary' },
  { title: 'unicode name',     body: { ...valid, name: 'आशा वर्मा' },       status: 200 },
];

for (const c of cases) {
  test(`validate/user: ${c.title} -> ${c.status}`, async ({ request }) => {
    const res = await request.post(URL, { data: c.body });
    expect(res.status()).toBe(c.status);
    if (c.field) {
      const fields = (await res.json()).error.details.map((d: any) => d.field);
      expect(fields).toContain(c.field);
    }
  });
}

test('malformed JSON -> 400 without internals', async ({ request }) => {
  const res = await request.post(URL, { headers: { 'Content-Type': 'application/json' }, data: '{"name": "Asha", ' });
  expect(res.status()).toBe(400);
  expect(await res.text()).not.toMatch(/exception|stack|\.php/i);
});

test('wrong content type -> 415', async ({ request }) => {
  const res = await request.post(URL, { headers: { 'Content-Type': 'text/plain' }, data: 'name=Asha' });
  expect(res.status()).toBe(415);
});

Each row is a test with a readable name in the report, and adding a case is one line.

Advertisement

Negative Testing Checklist

  1. Every required field missing, one at a time, and all together.
  2. Every field: wrong type, null, empty, too short, too long, boundary values.
  3. Every enum: unknown value, wrong case.
  4. Every format: invalid email, phone, date, URL, UUID.
  5. Body level: malformed, empty, array, oversized, duplicate keys, unknown fields.
  6. Headers: wrong or missing Content-Type, Accept, auth.
  7. Cross-field and business rules.
  8. Unicode, special characters and injection strings.
  9. Responses: correct 4xx, clear field errors, no internals, no side effects.
Advertisement

Interview Questions: Negative and Boundary API Testing

What is negative testing in APIs?

Sending invalid, missing, malformed or unexpected input to verify the API rejects it gracefully with the correct 4xx status, a clear error message, no data changes and no server errors.

What is boundary value analysis? Give an API example.

Testing at the edges of allowed ranges, where off-by-one bugs occur. For an age field allowed from 18 to 100, test 17, 18, 100 and 101 and expect 422, 200, 200 and 422.

Which status codes should negative API tests expect?

400 for malformed or unparseable requests, 422 for validation rule failures, 415 for unsupported content type, 413 for oversized payloads, 405 for wrong methods, 406 for unsupported Accept, 401/403 for auth problems and 404 for unknown resources. Never 5xx.

What is the difference between equivalence partitioning and boundary value analysis?

Equivalence partitioning groups inputs that should behave the same and tests one value per group. Boundary value analysis tests values at and around the edges of those groups. They’re usually used together.

How do you test a numeric field in an API?

Valid values, boundaries (min-1, min, max, max+1), zero, negative numbers, decimals, very large numbers, numbers as strings, null, missing field, booleans, arrays and objects.

What should a good validation error response contain?

A specific 4xx status, a machine-readable error code, field-level details naming each failing field, a clear human-readable message, all errors at once, a consistent format and no internal implementation details.

Why test duplicate keys in JSON?

JSON parsers handle duplicates differently (first wins, last wins or error). If different layers interpret them differently, attackers can bypass validation, for example passing a small amount through a gateway while the backend processes a large one.

How do you confirm a rejected request had no side effects?

After the negative request, query the relevant resources, lists, balances or counters (or the database) to confirm nothing was created, changed or triggered, such as emails or payments.

Is a 500 error acceptable for invalid input?

No. Invalid client input should always produce a 4xx validation error. A 500 indicates missing validation or an unhandled exception and should be logged as a defect.

How do you organise many negative test cases in automation?

Use data-driven tests: a table of inputs, expected status and expected error field, looped by one test function, so each row appears as a named test and new cases are added as data.

Advertisement
View the full API testing syllabus