Should I test that?

Should I test rate limits?

Verdict

Yes

Yes, give each group of rate-limited routes one integration test that lowers the limit in the test configuration, sends one request over it and expects a 429; do not test the counting algorithm of the rate-limiting library.

Why

Yes, test rate limits minimally: one test per group of limited routes that sends one request over the limit and expects a 429. The typical case is a web API whose middleware limits each client by IP address or API key. Blast radius is users, because a limit that stops counting lets one client slow the service for everyone, and Change frequency is rarely, because the limiter and the proxy in front of it change a few times a year. Detectability is eventually and Reversibility is with-effort, because a dead limit looks like normal traffic until a scraper finds it, and the fix is a config change plus cleanup of bot signups. Test cost is moderate, because the test runs the web layer with a lowered limit, so rule R12 gives Test minimally.

When the decision changes
WhenDecisionWhy
The limit guards login, password reset or one-time codes against guessingTest mandatory: assert that attempts over the limit for one account are refused when they come from several IP addressesBlast radius rises to safety-or-legal and Detectability to never, because a guessed password looks like a normal login
The limit caps text messages that you pay for per message, such as phone verification codesTest mandatory: assert that a request over the limit per phone number sends no messageBlast radius rises to money and Reversibility to impossible, because the provider bills every message it sends
New routes arrive most months, and each one must join a rate-limited groupTest: one test reads the route table and fails for any route that has no limiter and is not on an allowlistChange frequency rises to regularly, because every new route can miss the limiter
A managed API gateway enforces the limit, and only a deployed stage runs itTest it differently: alert when a client's request rate stays above its limit with no 429 responsesTest cost rises to heavy while Detectability stays eventually and Reversibility with-effort
The only failure you expect is a limit set too low, which users report as 429 errors the same dayDo not test the limit value; watch the 429 rate per route after each change to the valueDetectability falls to same-day and Reversibility to trivial, because a refused request stores nothing

What breaks if you don't test

A rate limit fails open with no error. A change made for another reason breaks the limiter: a proxy setting changed to fix addresses in the logs, a middleware moved below the router, or a second instance added. The express-rate-limit memory store does not share state across processes or servers, so three instances give each client three times the limit. Every request still gets a 200, and the first sign is a database alert during a scrape.

What you lose if you over-test

The common over-test sends the production limit, such as 1,000 requests per hour, and sleeps until the window resets. With an hourly window, that test adds up to an hour to CI and fails whenever a window boundary falls mid-run. Tests of a token bucket's refill timing check the library, which has its own tests.

How to test

Write integration tests with your framework's HTTP test client, and reset the limiter's store between tests. The minimum set:

  1. For each group of limited routes, set the limit to five in the test configuration, send six requests, and assert that only the sixth gets a 429, the status code from RFC 6585.
  2. Behind a proxy, give each request a different forged first entry in X-Forwarded-For and assert that they share one limit, because Express with trust proxy set to true takes the left-most entry as the client's address.

When the answer changes

  • The limit protects login, password reset or one-time codes.
  • A request over the limit costs you money, such as a text message.
  • A managed gateway that tests cannot run enforces the limit.

Real incident + Code example

The header that turned off the limit

On an Express API for a property-listing site I worked on, the search routes allowed 300 requests per client IP address per 15 minutes. After we moved behind a load balancer, a developer set app.set('trust proxy', true) so that the logs showed real client addresses. The express-rate-limit docs warn that this setting reads the left-most X-Forwarded-For entry, which a client can set. Eleven days later a scraper sent a new made-up address with each request and copied all 60,000 listings in one night, until a database CPU alert fired. We changed the setting to 1, which trusts only the address the load balancer appends, and added this test, which fails with true:

import request from "supertest";
import { buildApp } from "../src/app.js";

test("forged X-Forwarded-For entries share one client's limit", async () => {
  const app = buildApp({ searchLimit: 5 }); // production allows 300
  const statuses = [];
  for (let i = 0; i < 6; i++) {
    const res = await request(app)
      .get("/api/search?city=leeds")
      // The client forges the first entry; the load balancer appends the real address.
      .set("X-Forwarded-For", `10.9.8.${i}, 203.0.113.7`);
    statuses.push(res.status);
  }
  expect(statuses).toEqual([200, 200, 200, 200, 200, 429]);
});

FAQ

Should I write an integration test that verifies rate limits?

Yes, write one integration test per group of rate-limited routes. Lower the limit to five in the test configuration, send six requests and expect a 429 on the sixth, so the test never waits for a time window.

Should I test how my code handles a 429 from another API?

Yes, test your client's handling of another API's 429 when a dropped call loses work, such as a nightly sync. Point the client at a fake server that answers 429 with a Retry-After header, and assert that the client waits and retries.

Is rate limiting per IP address enough?

No, a per-IP rate limit does not stop credential stuffing, because attackers spread requests across many addresses, as OWASP's credential stuffing cheat sheet notes. Add a limit per account on login and one-time codes, and test it with requests from several addresses.