Q15 of 37 · API testing
How do you test an API that has rate limiting?
Short answer
Short answer: Three angles: assert the limit is enforced (burst beyond limit returns 429), assert the response carries informative headers (Retry-After, X-RateLimit-*), and assert the rate-limited path doesn't break downstream features. Use a separate test API key and isolate rate-limit tests from the rest of the suite.
Detail
Rate limits are easy to break, easy to mistest, and have surprising consequences when wrong.
What to test:
1. The limit is enforced:
test('limit of 100 req/min enforced', async ({ request }) => {
const responses = [];
for (let i = 0; i < 105; i++) {
responses.push(await request.get('/api/data'));
}
const status429 = responses.filter((r) => r.status() === 429);
expect(status429.length).toBeGreaterThanOrEqual(5);
});
2. Response headers are informative. Most API specs return:
X-RateLimit-Limit: total budget.X-RateLimit-Remaining: budget left in the current window.X-RateLimit-Reset: epoch when the window resets.Retry-After: seconds (or HTTP date) until retry is acceptable.
expect(res.headers()['retry-after']).toBeDefined();
expect(Number(res.headers()['x-ratelimit-remaining'])).toBeGreaterThanOrEqual(0);
3. Behaviour after the limit resets. Wait for the reset window, retry, assert success.
4. Limits are per-key, not global. Burn the limit on key A, confirm key B still works.
5. Limits don't apply to whitelisted paths. Health checks, billing webhooks — verify these aren't rate-limited.
Practical considerations:
Isolate rate-limit tests from the rest of the suite. If your "happy path" tests share an API key with rate-limit tests, the rate-limit test burns the budget and the next 100 happy-path tests fail with 429.
// Use a dedicated test key for rate-limit tests
test.describe('rate limits', () => {
test.use({ apiKey: process.env.RATE_LIMIT_TEST_KEY });
// ...
});
Don't run rate-limit tests in parallel with each other — they'll race for the same budget.
Mind the clock. Rate-limit windows reset on real time. Tests that wait for windows are slow; consider:
- A test environment with shorter windows (1-second buckets).
- Mocked clock if the API supports it.
- Skip in CI, run in nightly only.
Distributed rate limits (Redis-backed across multiple servers) sometimes have race conditions where a burst is briefly allowed past the limit. Tests should accept "approximately N" not "exactly N."