RizTech Academy logo
RizTech Academy
API TestingLesson 3 of 430 min

Designing API test cases: positive, negative, edge

You can send any request you like at an API — so which requests should you send? Firing random inputs finds the occasional bug by luck; designing test cases deliberately finds them reliably. The good news is you already learnt how in the manual-testing module: equivalence partitioning, boundary values, and positive/negative thinking apply to an API exactly as to a form — just aimed at a request instead of a screen. This lesson is designing API test cases, and the API-specific cases that have no UI equivalent.

The manual techniques, aimed at a request

An endpoint takes inputs (path params, query params, body fields) and every input is a candidate for the same analysis you did on form fields:

  • Equivalence partitioning — group each field's possible values into classes that should behave the same, and test one from each. For an amount field: a normal valid amount, zero, a negative, a huge one, a non-number, a missing value. You do not test every number — one per class.
  • Boundary values — test the edges of each valid range, which is where bugs cluster. If amount must be 1 to 100000, test 0, 1, 100000, 100001 — and the empty/absent case.
  • Positive and negative cases — the positive case (valid input → success, correct result) and, for every field, the negative cases (invalid input → the right rejection). Negative cases are where APIs most often fail, because developers test the happy path and forget the rest.

This is the same discipline as module 2, and it is the backbone of API test design. The difference is only that you express each case as a request in Postman and check the response, rather than typing into a form.

What to check on each response

For every case, you are checking more than "did it work" — you check three things:

  • The status code — the right one for the case: 2xx for valid, and the correct 4xx for each kind of bad input (400 for malformed, 422 for semantically invalid, 404 for missing, 409 for a conflict — the http lesson). A wrong-but-successful-looking 200, or a 500 where a 400 was due, is a finding.
  • The body — for a success, the correct data (verify the values, not just the shape); for an error, a clear message saying what was wrong.
  • The effect — did the request actually do (or not do) what it should to the data? A POST that returns 201 but did not really save, or a rejected request that did change something anyway, is a serious bug. Verify by fetching the record afterwards (a follow-up GET).

The negative cases an API must handle

APIs receive requests from anywhere — other services, scripts, a tampered client — so they must defend against input a UI would never send. Test these deliberately:

  • Missing required fields — omit each required field in turn; expect a clear 400/422, not a 500.
  • Wrong types — a string where a number is expected, "amount": "abc"; expect rejection, not a crash.
  • Malformed JSON — a broken body; expect a clean 400.
  • Extra/unexpected fields — send fields the API did not ask for; it should ignore them safely, not choke or, worse, act on them.
  • Out-of-range and boundary values — zero, negative, enormous, just over the limit.
  • Empty and null — "", null, empty arrays.
  • Injection-flavoured input — SQL/script-looking strings in text fields; the API must treat them as data, never execute them (a security concern the working-as-QA module returns to).

The recurring rule: the API must validate everything itself and never trust the client. A value the app form blocks can still arrive at the API from elsewhere — so if the server does not reject it, that is a real bug, even though no user could trigger it through the app. Testing this is exactly what UI-only testing cannot do.

API-specific cases with no UI equivalent

Some cases exist only at the API layer:

  • Auth and permissions. Call an endpoint with no token (expect 401), an invalid/expired token (401), and a valid token for the wrong user/role trying to read or change someone else's data (expect 403 — and this is the big one: an API that lets user A fetch user B's transaction by changing the id in the URL is a serious access bug, invisible from the UI). Test every protected endpoint this way.
  • Idempotency and retries. For money and other non-idempotent operations, send the same request twice (same idempotency key if the API uses one): does it create one record or two (the money-flow lesson, at the API)?
  • Pagination and filtering. Test page/limit and filter params at the API: correct counts, no missing/duplicated records across pages, sensible behaviour for an out-of-range page or a bad filter.
  • Concurrent requests. Two updates to the same record at once — does the API handle it (last-wins with a version check, or a 409), or corrupt the data?
  • Rate limits. If the API rate-limits, does it return 429 correctly past the limit?

These are where a QA who tests the API earns their keep — access-control and idempotency bugs are serious, common, and simply cannot be found by clicking the screen.

Check your work

Manual techniques, at the API. Equivalence partitioning and boundary values on every input (path/query/ body), and positive and negative cases for every field — the same module-2 discipline, expressed as requests instead of form entries. Negative cases are where APIs most often fail.

Check three things per case. The right status code (correct 2xx/4xx, never a misleading 200 or an undue 500); the body (correct values for success, clear message for errors); and the effect (verify with a follow-up GET that it did — or did not — change the data).

Negative cases an API must handle. Missing fields, wrong types, malformed JSON, extra fields, out-of-range/boundary, empty/null, injection-flavoured input. The rule: the API validates everything and never trusts the client — a value the form blocks can still arrive from elsewhere, so the server must reject it (only API testing reaches this).

API-specific cases. Auth (no/invalid token → 401) and permissions (valid token, wrong user/role → 403; the id-swap access bug); idempotency/retries (send twice → one record); pagination/filtering at the API; concurrent updates (409 or last-wins, not corruption); rate limits (429). These serious bugs are invisible from the UI.

Practice

  1. Take one create endpoint and design its test cases: the positive case, and negative cases from equivalence partitioning and boundary values on every field. Write them as a table.
  2. For a field with a valid range, list the exact boundary values you would send and the status you expect for each.
  3. Send the "must-handle" negative cases (missing field, wrong type, malformed JSON, huge value) and record which return the right 4xx and which wrongly return 200 or 500.
  4. Test a protected endpoint with no token, a bad token, and a valid token for the wrong user; confirm 401, 401 and 403 — and specifically try to fetch another user's record by changing the id.
  5. Send a non-idempotent request (a payment) twice and check whether it creates one record or two.
  6. Explain, with an example, why an input the app's form prevents can still be a real API bug.

Official documentation

Next: contracts, schemas and validating responses.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship