Positive, Negative, and Edge Case Testing

8 min read

If you only test the happy path, you're testing the 20% of inputs that produce 20% of the bugs. Real users do strange things — they leave fields blank, paste in 10,000-character strings, send emojis, double-click submit, lose their connection halfway through. The tests that catch the bugs that ship to production are the ones that anticipate this messiness. This lesson sorts API testing into three buckets — positive, negative, and edge — and shows how to grow coverage from each in a disciplined way.

The three buckets

A healthy API test suite has all three. Most beginners write only positives. Senior testers spend most of their time on negatives and edges, because that's where the bugs hide.

Positive testing

Positive tests confirm the API does what it should when everything is right. They're necessary but not sufficient.

For POST /api/orders:

  • Valid product, valid quantity, valid token → 201 Created with an order id.
  • Multi-item order with three different products → 201 with all items reflected.
  • Order at exactly the supported maximum quantity → 201.

Two rules:

  • Cover every documented success path. If the spec says the endpoint can be called by both admin and tester roles, write a positive test for each.
  • Assert on more than the status code. A 201 with a missing id field is still a bug.

If your positive coverage is solid and the suite is green, you've proved the feature can work. You haven't yet proved the feature handles users who don't follow the script.

Negative testing

Negative tests verify the API fails gracefully when input is wrong, missing, or malicious.

For the same POST /api/orders endpoint:

  • Missing required field → 400 with a clear error pointing to the missing field.
  • Wrong type (quantity: "two") → 400.
  • Invalid value (quantity: -5) → 400 or 422.
  • Reference to non-existent resource (productId: 999999) → 404 or 422.
  • No auth token → 401.
  • Expired token → 401.
  • Token from a user without permission → 403.

What you're checking:

  • Correct status code. A 500 instead of a 400 means the server crashed instead of validating cleanly. That's a bug.
  • Helpful error body. The error should tell the client (and your tests) which field was wrong and why. "error": "Invalid" tells nobody anything.
  • No data leak. A bad request shouldn't return a stack trace, an internal user id, or anything else the user shouldn't see.

A common QA truism: roughly 80% of bugs you find come from negative and edge testing, not positive testing. The exact percentage varies, but the lesson holds — bugs hide in the not-quite-right cases, not the textbook ones.

Edge case testing

Edge cases are the weird inputs and unusual scenarios at the boundaries of "what should the system do?" They're the third tier — beyond simply-wrong inputs into "I'm not sure what's right here."

Common edge categories:

  • Empty values. "name": "", [], {}. Are they treated as null? As valid? As errors?
  • Very long values. A name of 10,000 characters. A description of 1MB. The API may have an undocumented limit; finding it is your job.
  • Boundary numbers. Zero, negative, the maximum representable integer, fractions where integers are expected.
  • Date and time edges. Leap year, daylight saving transition, timezone offsets, far-future dates (year 9999), the Unix epoch (1970-01-01).
  • Unicode and special characters. Emoji 🎉, Chinese 中文, Arabic العربية, RTL text, zero-width spaces, NULL bytes. Some break encoding; some break length checks.
  • Concurrent requests. Two clients update the same resource simultaneously. Does the API serialise correctly, or do you end up with lost writes?
  • Large payloads. 100 items in a single request, 50MB upload, 10,000-key JSON object.
  • Idempotency. Send the same request twice — do you get one resource or two?
  • Pagination edges. No page parameter, page=0, page=-1, page=999999.
  • Auth edges. Token signed by a different key, token from a deactivated user, token whose role was revoked seconds ago.

Most teams miss the same handful of edges:

  • What happens with no pagination parameters? Some APIs silently return everything (memory bomb) instead of paginating.
  • What happens if a referenced resource is deleted mid-request? The dreaded race condition — usually a 500.
  • What happens with duplicate requests? Do you get one row or two?
  • What happens at the boundary of rate limits? The 100th request in a minute — accepted, or rejected with 429? Off-by-one bugs love rate limits.

A worked example: GET /api/products

Imagine a search endpoint that takes q (query string), page (1-indexed), and limit (default 20, max 100). A complete coverage list:

Positive (5):

  1. Common search: q=phone&page=1&limit=10 → 200 with up to 10 results.
  2. Empty results: q=zzz_no_match → 200 with [], not 404.
  3. Default pagination: omit page and limit → 200, 20 results, page=1.
  4. Maximum limit: limit=100 → 200 with up to 100 results.
  5. Page beyond first: page=2 → 200 with the next slice.

Negative (8):

  1. limit=0 → 400.
  2. limit=101 → 400 (over the max).
  3. page=0 → 400 (1-indexed).
  4. page=-1 → 400.
  5. q= (empty string) → 400 or returns all.
  6. limit=ten (not a number) → 400.
  7. No auth → 401 (if endpoint is protected).
  8. SQL injection in q → 200 with no results, not a 500.

Edge (7):

  1. Very long query q= + 5000 characters → graceful handling.
  2. Unicode q=café, q=中 → results returned, encoding correct.
  3. Emoji q=🎉 → graceful (results or empty), not 500.
  4. page=999999 → 200 with empty list, not 500.
  5. Concurrent requests with different page values → all return correctly.
  6. Whitespace-only q=' ' → consistent treatment.
  7. Special characters q=&%# → URL-encoded correctly, no 500.

Twenty tests for a single endpoint. Excessive? Maybe. But each one catches a class of bug, and most can be parameterised down to a single test function with a table of inputs.

Growing coverage in stages

A practical sequence for any new endpoint:

  1. Start with the happy path — one positive test that proves the endpoint works at all.
  2. Add the headline negatives — missing required fields, wrong types, missing auth.
  3. Add the headline edges — empty values, boundaries on numeric fields, the most likely race condition.
  4. Add security tests — injection inputs, auth bypass attempts, leaked data.
  5. Layer in performance and contract checks as the suite matures.

Stop when you've covered the documented contract plus the obvious gaps. Don't try to test every theoretical input — you'll never finish, and the maintenance cost will swamp the value.

⚠️ Common mistakes

  • Skipping negatives because "the UI prevents that." The UI is one of many clients. Mobile apps, scripts, and attackers can bypass UI validation. Test the API on its own merits.
  • Treating all edge cases as bugs. An API that returns [] for an unknown search term isn't broken — it's making a design choice. Confirm the choice with the team before raising a defect.
  • Asserting only on negative status codes. A 400 with a vague "error": "bad" body and a 400 with "field": "email", "code": "invalid_format" are very different in usefulness. Test the error shape, not just the status.

🎯 Practice task

Build a full coverage list for an endpoint. 30 minutes.

  1. Pick an endpoint you've worked with (or POST https://jsonplaceholder.typicode.com/posts). List the parameters and any validation rules.
  2. Create three lists: positive, negative, edge. Aim for at least 5 in each.
  3. For each negative test, predict the expected status code (400 vs 401 vs 403 vs 422). If you're not sure, that's exactly the question worth asking the engineering team.
  4. For each edge test, write a one-sentence rationale: "What bug does this catch that the positives don't?"
  5. Run two of your negative tests with curl against a real endpoint. Compare actual to expected — when they differ, you've either found a bug or a documentation gap.
  6. Stretch: review your team's most recent API-related production bug. Check whether your three lists would have caught it. If not, what kind of test would have? Add it to your team's standard checklist.

You can now design a coverage plan that finds bugs before users do. That wraps up Chapter 2. The rest of the course goes deep into the patterns these test cases need: authentication, request shapes, response validation, GraphQL, contracts, and full test strategies.

// tip to track lessons you complete and pick up where you left off across devices.