How to Write API Test Cases: Template + Examples (2026)
Quick answer
A well-written API test case has five parts: an ID and title that name exactly what's being tested, a precondition (what state the system must be in), the request (method, endpoint, payload), the expected result (status code and response shape, not just "it works"), and a postcondition confirming no unintended side effects. Writing test cases in this template before automating them keeps coverage intentional — positive, negative, and boundary cases planned on purpose, not discovered by accident when something breaks in production.
Reviewed by Rishi Gaurav
An API test case is a documented, specific scenario describing exactly what to send to an endpoint and exactly what should come back — distinct from the test automation code that later executes it. Writing the test case first, even in outline form, forces a decision about what "correct" means before you write the assertion that checks it, and produces something a non-engineer can review before any code exists.
This guide gives a reusable template, worked examples across positive, negative, boundary, and security scenarios, a naming convention, and how each written test case maps directly onto an automated test in whichever framework you use.
Table of Contents
- The API Test Case Template
- Naming Convention
- Positive Test Case Examples
- Negative Test Case Examples
- Boundary Test Case Examples
- Security Test Case Examples
- How Many Test Cases Does One Endpoint Need?
- From Test Case to Automated Test
- Common Mistakes When Writing Test Cases
- FAQ
The API Test Case Template
| Field | Description |
|---|---|
| ID | A unique, sortable identifier (e.g. TC_Users_Get_001) |
| Title | A one-line description of exactly what's being tested |
| Precondition | What state the system must be in before this test runs |
| Method & Endpoint | The HTTP method and path under test |
| Request Payload | Headers, query params, and body sent with the request |
| Expected Status Code | The exact status code expected — not "success," a number |
| Expected Response | The response shape or specific field values expected |
| Postcondition | What should (or should not) have changed as a result |
Every field earns its place: skipping Precondition produces a flaky test that only passes when run in a specific order; skipping Postcondition misses side effects a status-code-only check can't catch (a DELETE that returns 200 but doesn't actually delete anything).
Naming Convention
A consistent pattern makes a test report scannable without opening each case individually:
TC_[Feature]_[Action]_[Condition]_[ExpectedResult]
Examples:
TC_Users_Get_ValidID_Returns200
TC_Users_Get_NonexistentID_Returns404
TC_Users_Create_MissingEmail_Returns400
TC_Users_Create_DuplicateEmail_Returns409
TC_Posts_Delete_UnauthorizedUser_Returns403
The exact format matters less than consistency across the whole suite — pick one, document it once, and every test case's purpose is readable from its name alone in a CI report.
Positive Test Case Examples
| ID | Title | Precondition | Request | Expected |
|---|---|---|---|---|
| TC_Users_Get_001 | Get an existing user by valid ID | User with ID 1 exists | GET /users/1 | 200, body matches user schema |
| TC_Posts_Create_001 | Create a post with valid required fields | User with ID 1 exists | POST /posts with {title, body, userId: 1} | 201, response echoes submitted fields plus a new id |
| TC_Posts_Update_001 | Update an existing post's title | Post with ID 1 exists | PUT /posts/1 with updated title | 200, title reflects the new value |
Negative Test Case Examples
| ID | Title | Precondition | Request | Expected |
|---|---|---|---|---|
| TC_Users_Get_002 | Get a user with a nonexistent ID | User with ID 999999 does not exist | GET /users/999999 | 404, error body has consistent shape |
| TC_Posts_Create_002 | Create a post missing the required title field | — | POST /posts with {body, userId: 1} (no title) | 400/422, error message names the missing field |
| TC_Posts_Create_003 | Create a post with a wrong data type | — | POST /posts with userId: "one" (string, not integer) | 400/422, rejected before reaching business logic |
Ready to shift left with your API testing?
Try our no-code API test automation platform free. Generate tests from OpenAPI, run in CI/CD, and scale quality.
Boundary Test Case Examples
Bugs cluster at the edges of valid ranges far more often than in the middle, which is why boundaries deserve their own dedicated cases rather than being left to chance:
| ID | Title | Precondition | Request | Expected |
|---|---|---|---|---|
| TC_Posts_List_001 | Request page 0 of a paginated list | — | GET /posts?_page=0 | Defined behavior (either treated as page 1, or a 400) — whichever the spec documents, tested explicitly |
| TC_Posts_List_002 | Request a page number past the last page | Only 10 pages exist | GET /posts?_page=999 | 200 with an empty result array, not an error |
| TC_Posts_Create_004 | Submit a title at the maximum allowed length | Max length is documented as 255 chars | POST /posts with a 255-char title | 201, accepted at exactly the limit |
| TC_Posts_Create_005 | Submit a title one character over the maximum | — | POST /posts with a 256-char title | 400/422, rejected just past the limit |
Security Test Case Examples
| ID | Title | Precondition | Request | Expected |
|---|---|---|---|---|
| TC_Users_Get_003 | Access a resource without authentication | — | GET /account with no Authorization header | 401 |
| TC_Users_Get_004 | Access another user's private resource (BOLA check) | User A is authenticated; resource belongs to User B | GET /users/{userB_id}/private-data as User A | 403 (not 200 with User B's data) |
| TC_Auth_Login_001 | Attempt login with an expired token on a protected route | Token issued more than its TTL ago | GET /account with an expired Authorization header | 401, not a degraded but authenticated response |
See our full checklist for the complete set of functional, security, and performance items these examples are drawn from, and the OWASP API Top 10 testing guide for deeper security-specific cases like these.
How Many Test Cases Does One Endpoint Need?
Enough to cover the happy path, every documented error status, key boundary values per input field, and at least one authorization case. For a typical CRUD endpoint, that's commonly 8–15 test cases:
- 1–2 positive cases (valid input, valid ID)
- 2–4 negative cases (missing fields, wrong types, invalid IDs)
- 2–3 boundary cases (min/max length, empty, pagination edges)
- 1–2 security cases (unauthenticated, unauthorized/BOLA)
- 1–2 idempotency or side-effect cases (retry, verify postcondition)
Endpoints with more complex business logic — conditional validation, multi-step workflows, state machines — need proportionally more.
From Test Case to Automated Test
Each row in the tables above maps directly onto one automated assertion. TC_Users_Get_002 (nonexistent ID returns 404) in a few frameworks:
# pytest
def test_get_user_nonexistent_id_returns_404(api_session, base_url):
response = api_session.get(f"{base_url}/users/999999", timeout=10)
assert response.status_code == 404
// REST Assured
@Test
void getUserNonexistentIdReturns404() {
given()
.when()
.get("/users/999999")
.then()
.statusCode(404);
}
# Karate
Scenario: get user with nonexistent ID returns 404
Given path 'users/999999'
When method get
Then status 404
See the pytest, REST Assured, Playwright, Cypress, and Karate tutorials for the complete pattern in each framework — every one of them accepts test cases written in this template as direct input.
Free Guided worksheet
Build Your Testing Strategy in 30 Minutes
A structured worksheet that walks you through defining your testing strategy in 30 minutes. Cover architecture, tools, layers, and team responsibilities.
Download FreeCommon Mistakes When Writing Test Cases
- Writing "it works" as the expected result instead of the exact status code and response shape — an ambiguous expectation produces an ambiguous automated assertion.
- Skipping the precondition, producing a test that only passes when run after another specific test happens to have created the data it needs.
- Testing only the happy path per endpoint and treating negative/boundary cases as optional extras rather than an equal third of the suite.
- No postcondition check, so a
DELETEthat returns200without actually deleting anything passes undetected. - Inconsistent naming across the suite, making a CI failure report unreadable without opening every individual case.
- Writing test cases that describe implementation instead of behavior — "calls the UserService" instead of "returns 200 with the user's email field," which breaks the moment the implementation is refactored even though behavior is unchanged.
Frequently Asked Questions
What are the essential parts of an API test case? An ID and title, a precondition, the request itself, the expected result (status code and response shape), and a postcondition confirming no unintended side effects.
What is the difference between positive and negative test cases? A positive case sends valid input and expects success. A negative case deliberately sends invalid input and expects the API to reject it correctly.
What are boundary test cases in API testing? Cases targeting the edges of valid input ranges — minimums, maximums, empty values, and pagination edges — where bugs cluster more often than in the middle of a valid range.
How do I name API test cases consistently?
A common pattern is TC_[Feature]_[Action]_[Condition]_[ExpectedResult]. The exact format matters less than applying it consistently across the suite.
How many test cases does one API endpoint need? Commonly 8–15 for a typical CRUD endpoint: the happy path, documented error statuses, boundary values, and at least one authorization case.
Should I write test cases before or after building the automated tests? Before, at least in outline — it forces a decision about what "correct" means before you write the assertion, and produces a reviewable artifact.
Key Takeaways
- Precondition and postcondition are the most commonly skipped template fields — and the ones that catch flaky ordering bugs and silent side effects.
- "It works" is not an expected result. Write the exact status code and response shape.
- Positive, negative, and boundary cases are roughly equal thirds of a complete suite, not happy-path-plus-afterthoughts.
- A consistent naming convention makes CI failure reports scannable without opening every case.
- 8–15 test cases per typical CRUD endpoint is a reasonable baseline; complex business logic needs more.
- Every written test case maps directly onto one automated assertion, regardless of which framework executes it.
Related Articles
- API Testing Checklist — the complete functional, security, and performance checklist these examples are drawn from.
- Types of API Testing — the broader categories these test cases fall into.
- API Testing with Python: pytest + requests Tutorial — turning written test cases into automated Python tests.
- Common API Security Vulnerabilities and How to Test Them — deeper security-specific test case examples.
Generate Test Cases Automatically from Your OpenAPI Spec
Writing 8-15 test cases per endpoint by hand scales linearly with your API's size. Total Shift Left generates positive, negative, and boundary test cases directly from your OpenAPI spec — the same structure as the template above, covering every endpoint automatically and regenerating when the spec changes.
Start your free trial to see generated test cases for your own API, or see plans and pricing if you're already evaluating.
Ready to shift left with your API testing?
Try our no-code API test automation platform free.