REST API Testing Best Practices: A Practical Checklist
Most API test suites fail in one of two ways: they only check status === 200 and miss real bugs, or they assert every byte of the response and break on every harmless change. This checklist is the middle path — what experienced QA engineers and backend developers actually verify, and why.
1. Assert the contract, not the payload
For each endpoint, verify the things clients depend on:
- Status code — and the right one. A create should return
201, not a generic200. - Content type —
application/jsonwhen the client will parse JSON. - Shape — required fields exist and have the right type (
idis a number,createdAtparses as a date). Don't pin exact values that legitimately change. - Semantics — the response reflects the request: the
nameyou sent is thenameyou got back.
// Example post-response test (RestRuno rr.* API)
rr.test('creates the user', () => rr.expect(rr.response.status).toBe(201));
rr.test('returns a numeric id', () => rr.expect(typeof rr.response.body.id).toBe('number'));
rr.test('echoes the name', () => rr.expect(rr.response.body.name).toBe('Ada'));
2. Test the unhappy paths — that's where the bugs live
Happy-path tests confirm the demo works. Unhappy-path tests find defects. For every endpoint, cover at minimum:
- Missing required fields → expect
400or422with a useful error body, not a500. - Wrong types — a string where a number is expected,
nullwhere an object is expected. - Boundary values — empty strings, 0, negative numbers, very long strings, Unicode and emoji.
- Unknown IDs → expect
404, and verify it does not leak whether the resource exists to unauthorized callers. - No auth / expired auth → expect
401; valid auth but wrong owner → expect403(see the API security checklist).
3. Make tests independent and repeatable
- Chain state through variables, not copy-paste. Log in once, store the token in a variable, and let every request reference
{{token}}. When a login response changes, you fix one script. - Create what you need, clean up what you create. A test that depends on "user 42 already exists in staging" will break the day staging is rebuilt.
- Never hardcode environments. Use
{{baseUrl}}and switch between local, staging and production with environments — the same tests should run against all three.
4. Externalize test data
When the same endpoint must be verified with 20 input combinations, don't write 20 requests. Keep one request and drive it from a CSV or JSON file — one row per case, each column a variable. This keeps coverage visible in a single table and makes adding a case a one-line change. See the full guide to data-driven API testing.
5. Measure more than correctness
- Response time — assert an upper bound (
rr.expect(rr.response.timeMs).toBeLessThan(1500)) so performance regressions fail loudly instead of degrading silently. - Response size — a list endpoint that suddenly returns 5 MB usually means a missing pagination or an over-eager serializer.
- Headers — caching (
Cache-Control), CORS and security headers are part of the contract too.
6. Keep the suite reviewable
API tests are code. They deserve version control, code review and readable diffs like any other code. Storing collections as plain JSON files in the repository — next to the API they test — means a new endpoint and its tests land in the same pull request. Here's how to version-control API collections with Git.
Quick checklist
- ✔ Right status code, content type, shape and semantics
- ✔ Missing fields, wrong types, boundaries, unknown IDs
- ✔ 401 without auth, 403 with the wrong owner
- ✔ No hardcoded hosts or tokens — variables and environments
- ✔ Repeatable: tests create and clean their own data
- ✔ Data-driven where inputs multiply
- ✔ Response-time assertions on critical endpoints
- ✔ Collections in Git, reviewed like code
Put this checklist into practice
RestRuno is a free, local-first REST client with scripting, tests, environments and a data-driven runner. Your collections are plain JSON files — commit them next to your code.
Download RestRuno — free