API Testing Best Practices
This page lists good habits for testing APIs, whether you click through Postman or write suites in code. They help your tests catch the bugs that matter โ wrong data, broken rules, security holes โ and keep the suite reliable enough to block a release.
Each practice has:
- Do โ the good way.
- Why โ the reason in simple words.
- An example, where it helps. Examples use the practice APIs.
Read it once after you finish Part 2 of the API testing cheat sheet, then use the checklist at the end on every pull request that adds API tests.
Contentsโ
- Start From the Contract
- Check the Whole Response
- Negative Tests First-Class
- Auth and Permissions
- Own Your Test Data
- Schemas and Contracts
- Environments and Secrets
- Reliability
- Mocks, Used Carefully
- Organising the Suite
- Reporting API Bugs
- Short Checklist Before You Merge
1. Start From the Contractโ
In short: read the API's specification first, then test that reality matches it.
- Do get the OpenAPI/Swagger file (or write down the contract from the team) before testing. Why: you can't say a response is wrong without knowing what's right.
- Do build an endpoint inventory: method, path, auth needed, owner. Why: it's your coverage map โ untested endpoints are obvious.
- Do test that undocumented behaviour doesn't exist (hidden endpoints, extra fields). Why: undocumented endpoints are rarely protected as carefully.
2. Check the Whole Responseโ
In short: status, body, schema, headers and saved state โ a 200 alone proves very little.
- Do assert specific values, not just that a field exists.
Why:
totalpriceexisting doesn't mean it's the price you sent. - Do read data back with a
GETafter every write. Why: some APIs echo your input in the response without saving it. - Do check
Content-Typeand important headers. Why: clients break when the format changes silently.
# Weak
assert res.status_code == 200
# Strong
assert res.status_code == 200
assert res.json() == sent # stored exactly what we sent
assert requests.get(f"{BASE_URL}/booking/{booking_id}").json() == sent # and it's really saved
3. Negative Tests First-Classโ
In short: most API bugs hide in invalid input โ give negative tests the same care as happy paths.
- Do test each required field missing, one at a time. Why: on Restful Booker, a missing field returns 500 โ a crash found in one test.
- Do test wrong types and impossible values (
"abc"for a price, check-out before check-in). Why: silent acceptance (price stored asnull) corrupts data far from where it started. - Do expect a specific 4xx and a helpful message. Why: "400 โ lastname is required" lets clients fix their request; a 500 doesn't.
- Do write the test for the correct behaviour even when the API is wrong. Why: the failing test is the bug report; mark it as a known failure until it's fixed.
4. Auth and Permissionsโ
In short: test every protected endpoint with no user, the wrong user and the wrong role.
- Do keep test accounts for two users of each role. Why: you can't test "user A can't see user B's data" with one user.
- Do try another user's ids on every endpoint that takes an id. Why: broken object-level authorization (BOLA) is the most common serious API flaw.
- Do check tokens expire and that logout really invalidates them. Why: a stolen token that works forever is a permanent breach.
- Do fetch fresh tokens in a fixture, never hard-code them. Why: hard-coded tokens expire and make tests fail for the wrong reason.
5. Own Your Test Dataโ
In short: each test creates the data it needs and doesn't rely on anything already there.
- Do create data in the test (or a fixture) with a unique value. Why: shared demo data like "booking 1" is changed or deleted by others โ Restful Booker's booking 1 doesn't always exist.
- Do use builders with valid defaults and override one field per test. Why: readers see instantly which field the test is about.
- Do clean up what you created when the environment is shared. Why: thousands of leftover test records slow everyone down.
6. Schemas and Contractsโ
In short: validate shapes automatically, and let consumers and providers agree contracts in code.
- Do validate every response against a JSON Schema (or the OpenAPI spec). Why: one line catches wrong types and missing fields on every call.
- Do keep schemas in version control next to the tests. Why: a schema change shows up in review, not in production.
- Do use consumer-driven contract tests (Pact) between internal services. Why: the provider learns it's about to break a consumer before deploying.
7. Environments and Secretsโ
In short: the same tests run anywhere by changing configuration, never code.
- Do put base URLs and credentials in environment variables or Postman environments. Why: switching from staging to a local build is one setting.
- Don't commit tokens, API keys or real passwords โ in code or in exported Postman collections. Why: exported collections are often shared publicly by accident.
- Do mark destructive tests (deletes, bulk updates) and never run them against production. Why: a test that deletes "all test users" is one wrong URL away from deleting real ones.
8. Reliabilityโ
In short: API suites should be the most stable part of your testing โ keep them that way.
- Do set timeouts on every request. Why: a hanging call shouldn't hang the whole CI job.
- Do make tests independent and safe to run in parallel. Why: fast suites get run on every change.
- Don't retry failed requests inside tests to "make them pass". Why: retries hide real errors โ unless you're testing retry behaviour.
- Do check an environment health endpoint before the suite starts. Why: "environment down" is reported once, not as 300 failures.
9. Mocks, Used Carefullyโ
In short: mock what you don't own; test what you do own for real.
- Do mock third parties (payments, email, SMS) in most tests. Why: sandboxes are slow, rate-limited, and hard to put into error states.
- Do keep a small set of tests against the real integration. Why: mocks don't notice when the real provider changes.
- Do generate mocks from the contract (OpenAPI โ Prism) where possible. Why: hand-written mocks drift away from reality.
10. Organising the Suiteโ
In short: group by resource, tier by speed, and keep helpers in one place.
- Do organise tests by resource (
bookings/,auth/,payments/). - Do tag a small smoke tier (health, auth, one CRUD flow) for every deploy.
- Do wrap endpoints in a client class/module, so tests call
create(booking), not raw URLs. Why (all): a new team member finds and adds tests quickly, and a path change is a one-line fix.
11. Reporting API Bugsโ
In short: an API bug report is a runnable request plus expected vs actual.
- Do include a copy-pasteable
curlcommand. Why: developers reproduce it in seconds, without your tools. - Do include the timestamp (with time zone) and any request/correlation id header. Why: developers can find the exact server log line.
- Do mask tokens and personal data. Why: bug trackers are widely shared.
See the API bug report example.
12. Short Checklist Before You Mergeโ
- Every endpoint touched has happy, negative and auth tests
- Writes are read back; values and schema asserted, not just status
- Tests create their own data with unique values
- No tokens, keys or passwords in code or collections
- Base URL and credentials come from configuration
- Timeouts set; no retries hiding failures
- Runs in parallel and in any order
- Known API bugs are marked as expected failures with a ticket id
- Smoke tier tagged and fast
- Endpoint inventory / coverage updated
Need more detail? Cheat sheet ยท Quick reference ยท Full guide