Skip to content
QA Knowledge Hub

Test cases for a REST API endpoint (CRUD)

Updated 2026-11-13 · 7 min read

The API version of 'test a login page'. These 35 cases cover POST, GET, PUT, PATCH and DELETE with status codes, validation, idempotency, authorisation, performance and contract checks.

Assumes a JSON API for books at /api/v1/books that needs a bearer token. A book has a server-generated id, title (required, 1 to 120 characters), author (required), price (number, 0 or more) and isbn (unique). The status codes below follow one common convention; where your API documents a different one, test against the documentation.

How to answer in an interview

Ask first: is there an API specification (OpenAPI), how does authentication work, what are the validation rules, which status codes does the team use for create, delete and validation errors, are there roles, and does the API support pagination, conditional requests (ETag) or idempotency keys? Then go method by method (POST, GET, PUT, PATCH, DELETE), and finish with cross-cutting checks: headers, security, performance and contract. Say you would check the database or a follow-up GET, not just the status code.

Scope and test data

Use a test environment with two test users, [email protected] (role editor) and [email protected] (role viewer), each with their own token. Example book: title "Testing Notes", author "A. Tester", price 499, ISBN from a fake range agreed with the team. Example rules: list page size defaults to 20 with a maximum of 100. These are designed cases, not an execution report.

Create (POST)

IDCategoryTest caseExpected result
R01FunctionalPOST a valid book201; body has a new id and the sent fields; Location header points to /api/v1/books/ plus the new id
R02NegativePOST without title400 or 422 as documented; error names the missing field
R03BoundaryPOST a title of 120 characters, then 121120 accepted (201); 121 rejected with a validation error
R04NegativePOST price as the string "abc" and as −1Validation error for each, never a 500
R05NegativePOST a second book with an existing ISBN409 with a message that names the conflict
R06NegativePOST valid JSON with Content-Type: text/plain415
R07NegativePOST malformed JSON (a missing closing brace)400; no stack trace or framework details in the body
R08NegativePOST with id or createdAt set by the clientFields ignored or rejected as documented; server values used
R09FunctionalPOST the same body twice (a client retry)Two books, or one if an idempotency key is supported and the same key was sent; documented either way

Read (GET)

IDCategoryTest caseExpected result
R10FunctionalGET an existing book by id200; body matches what was created
R11NegativeGET an id that does not exist404
R12NegativeGET with an id in the wrong format (abc for a numeric id)400 or 404 as documented, never a 500
R13FunctionalGET the list with no parameters200; at most 20 items; pagination information present
R14BoundaryGET a page beyond the last page200 with an empty list, or as documented; not an error
R15FunctionalGET the list filtered by author and sorted by priceOnly matching books, in the right order

Update (PUT and PATCH)

IDCategoryTest caseExpected result
R16FunctionalPUT a full valid body to an existing book200 or 204; a follow-up GET shows every field changed
R17NegativePUT a body without authorValidation error; the stored author is not silently set to null
R18FunctionalPATCH only priceOnly price changes; other fields unchanged
R19NegativePUT and PATCH to an id that does not exist404 (or 201 for PUT if create-by-PUT is documented)
R20FunctionalSend the same PUT twiceSame final state after both; no duplicate side effects
R21NegativeUpdate with an If-Match ETag from an older version, if ETags are supported412; the newer data is not overwritten
R22NegativePUT to book 5 with "id": 6 in the bodyRejected, or the URL id wins, as documented; book 6 is untouched

Delete (DELETE)

IDCategoryTest caseExpected result
R23FunctionalDELETE an existing book204 (or 200); a follow-up GET returns 404
R24FunctionalDELETE the same book again404 or 204 as documented; never a 500
R25NegativeDELETE a book that orders still refer to409, or the documented cascade or soft delete; no broken references

Methods, headers and contract

IDCategoryTest caseExpected result
R26NegativeSend PATCH to the collection /api/v1/books405 with an Allow header listing supported methods
R27FunctionalCheck response headers on success and errorContent-Type is JSON on both; error bodies use one documented format
R28FunctionalValidate every response against the OpenAPI schema, including errorsAll responses match; no undocumented fields such as internal ids

Security

IDCategoryTest caseExpected result
R29SecurityCall each method with no token and with an expired token401 for both; no data returned
R30SecurityAs Ravi, GET, PUT and DELETE a book that belongs to Asha, if books are owned per user403 or 404; nothing changed
R31SecurityAs Ravi (viewer), POST, PUT and DELETE403; nothing changed
R32SecurityPOST a title containing ' OR 1=1 -- and another containing a script tagStored and returned as plain text or rejected; no database error leaked
R33SecurityAdd "role": "admin" or another server-only field to a PATCHField ignored or rejected; not saved (mass assignment)

Performance

IDCategoryTest caseExpected result
R34PerformanceGET by id at the expected request rateResponse times within the agreed limit; no errors
R35PerformanceGET the list with limit=100000Capped at the maximum (100) or rejected; the server does not try to return everything

Practise it

The free ShopLite practice app has a cart API with create, update and delete on one resource (POST /api/cart/items, then PATCH and DELETE on /api/cart/items/ plus a product id), behind a login. Adapt R02, R04, R23, R24 and R29 to it with Postman or Playwright's request API. Its conventions differ from the example above in places (its DELETE returns 200 with the updated cart rather than 204), which is the point: check each response against the app's own documentation, not against a convention from memory. The app also has deliberate bugs, so some cases should fail.

What to prioritise

R30 and R31 (authorisation), R01 and R23 with a follow-up GET (data really changed), and R04, R07 and R12 (bad input never gives a 500). The most common interview mistake is checking only the status code: say how you would check the body, the headers and the stored data too.

Downloads

Quick answers

Should a failed validation return 400 or 422?

Both are used. RFC 9110 defines 422 (Unprocessable Content) for well-formed content the server cannot process, and many APIs still return 400 for every bad request. Test that the API is consistent with its own documentation and never returns a 500 for bad input.

Is POST idempotent?

No. RFC 9110 lists PUT, DELETE and the safe methods (such as GET) as idempotent; POST is not. Sending the same POST twice may create two resources unless the API supports something like an idempotency key.

What is the most important security test for a CRUD API?

Changing the ID in the URL to another user's resource (R30). OWASP lists Broken Object Level Authorization as API1 in its 2023 API Security Top 10.

Sources

Want a project you can show in interviews?

The portfolio project kit walks you through testing a deliberately buggy app with Selenium or Playwright and API tests, then explaining it. The app itself is free.