Q15 of 37 · API testing

How do you test an API that has rate limiting?

API testingMidapirate-limitingheaderstest-isolation

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."

// WHAT INTERVIEWERS LOOK FOR

All three test angles (limit enforced, headers correct, reset behaviour), isolating rate-limit tests with a dedicated key, and awareness that distributed rate limits aren't bit-exact.

// COMMON PITFALL

Running rate-limit tests in parallel with the rest of the suite using the same API key. The rate-limit test passes; the next 200 tests get 429 and the suite collapses.