- Test a REST endpoint on four layers: status code, body schema, headers and latency. A test asserting only on status passes while the API returns the wrong data.
- GET, PUT and DELETE must be idempotent. Call each twice and confirm the state and status are stable after the first call.
- The endpoints most likely to be under-tested are the error branches — 400, 401, 403, 409 and 422.
- Pagination bugs are common and quiet: records appearing on two pages or skipped between them rarely surface until a customer notices.
How to test a REST API
REST testing means sending an HTTP request to an endpoint and asserting on the complete response — not merely that something came back. The workflow is the same for every endpoint: establish the happy path, then systematically attack it.
- Send the valid request. Assert the status code, validate the body against a schema, and check the latency against a budget.
- Remove each required field in turn. Every one should produce a 400 or 422 with a message naming the offending field — not a 500.
- Send wrong types. A string where an integer belongs, an array where an object belongs, a 40,000-character name. Validation should reject these cleanly.
- Strip the credentials. Expect 401, and confirm the response body leaks no information about whether the resource exists.
- Use another user's credentials. Expect 403 or 404. If you get 200, you have found the most common serious API vulnerability — see security testing.
- Request something that doesn't exist. Expect 404, not a 500 from an unhandled null.
HTTP status codes worth asserting
| Code | Means | Test that produces it |
|---|---|---|
| 200 OK | Successful read or update | Valid GET or PUT |
| 201 Created | Resource created | Valid POST — assert the Location header too |
| 204 No Content | Success, nothing to return | Valid DELETE |
| 400 Bad Request | Malformed syntax | Invalid JSON, wrong types |
| 401 Unauthorized | Missing or invalid credentials | Omit the auth header |
| 403 Forbidden | Valid credentials, insufficient permission | Another user's resource |
| 404 Not Found | Resource doesn't exist | A plausible but absent ID |
| 409 Conflict | State conflict | Duplicate unique field |
| 422 Unprocessable | Syntactically valid, semantically wrong | End date before start date |
| 429 Too Many Requests | Rate limited | Burst past the limit — assert Retry-After |
Treat any unexpected 500 as a test failure, never a pass. A 500 on malformed input means validation is missing and an unhandled exception reached the top of the stack — which usually also means a stack trace was returned to the caller.
Testing idempotency
An idempotent method produces the same result regardless of how many times it is called. Under the HTTP specification, GET, PUT and DELETE are idempotent; POST generally is not. This matters because clients retry — on timeouts, on network failures, on user impatience.
To test it, issue the same request two or three times and assert the resource state is unchanged after the first call. The revealing case is DELETE: the first call returns 204, and the second should return 204 or 404 — never a 500 from code assuming the record exists.
For POST endpoints that must not double-apply — payments especially — the usual mechanism is an idempotency key supplied by the client. Test that sending the same key twice creates one resource and returns the original result the second time.
Testing pagination
Pagination defects are quiet and common. Cover five cases:
- Page size is respected. Requesting 10 returns exactly 10 while more remain.
- The total count is accurate and consistent with the number of pages.
- The last page returns a partial set without erroring.
- An out-of-range page returns an empty list, not a 500.
- No record appears on two pages or is skipped between them. Offset pagination over a table that is being written to concurrently will shift records between pages — which is why cursor pagination exists.
Also test the limits: a page size of 0, of -1, and of 100,000. The last one is a denial-of-service vector if the API obediently serialises a hundred thousand records.
Validating the response body
Spot-checking two fields is the most common weakness in REST suites. Validate the entire body against a schema instead — field names, types, nullability and required properties. That is the difference between an integration test and a contract test, and it is what catches the field that silently changed from a number to a string.
Where a JSON Schema or OpenAPI document exists, use it directly. Where none exists, generate one from live responses and treat it as a baseline — an inferred schema is far better than none.
Authentication in tests
Most meaningful endpoints sit behind authentication, and the way tests handle it determines how much they are worth.
- Use a dedicated test account with least privilege, not a developer's personal credentials.
- Inject secrets from the CI secret store. Never commit a token, even to a private repository.
- Create at least two test users. Cross-user authorization tests are impossible with one, and they are the tests that find the most serious flaws.
- Test token expiry. An expired token should return 401 and the client should refresh rather than loop.
Test your API without writing the tests
Paste a URL. Flasqo discovers your endpoints, generates the suite and runs it — free, no credit card.
Start testing free