Skip to main content

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.
How to use this page

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โ€‹

  1. Start From the Contract
  2. Check the Whole Response
  3. Negative Tests First-Class
  4. Auth and Permissions
  5. Own Your Test Data
  6. Schemas and Contracts
  7. Environments and Secrets
  8. Reliability
  9. Mocks, Used Carefully
  10. Organising the Suite
  11. Reporting API Bugs
  12. 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: totalprice existing doesn't mean it's the price you sent.
  • Do read data back with a GET after every write. Why: some APIs echo your input in the response without saving it.
  • Do check Content-Type and 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 as null) 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 curl command. 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