Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 3 min read

12 pagination test cases every API client should pass

Most pagination bugs I've seen weren't in the happy path. Page 1 works, page 2 works, and then someone asks for page 0, or the last page has exactly limit items, or a cursor gets cut off in a URL, and the client either l

Most pagination bugs I've seen weren't in the happy path. Page 1 works, page 2 works, and then someone asks for page 0, or the last page has exactly limit items, or a cursor gets cut off in a URL, and the client either loops forever or quietly drops data.

I build testingapis.com, a free fake API for testing clients, and while adding pagination endpoints to it I wrote down every edge case I could think of. Here's the list. The requests point at my API so you can see a real response, but the point is to run the same cases against your own API.

The five styles

Before the test cases, a quick reminder that "pagination" means five different things:

Style Request Client stops when
Offset ?offset=20&limit=10 hasMore is false or offset >= total
Page number ?page=2&size=10 last is true
Cursor ?cursor=eyJsYXN0SWQiOjJ9 next_cursor is null
Keyset ?after_id=20&limit=10 next_after_id is null
Link header Link: <...page=3>; rel="next" No rel="next"

A cursor response looks like this:

curl "https://api.testingapis.com/api/pagination/cursor?limit=2"
{
  "object": "list",
  "data": [
    { "id": 1, "name": "Smart Headset", "price": 42.01 },
    { "id": 2, "name": "Compact Speaker", "price": 79.02 }
  ],
  "has_more": true,
  "next_cursor": "eyJsYXN0SWQiOjJ9"
}

The 12 cases

# Test case Request What a good API does
1 First page /offset?offset=0&limit=10 Items 1–10, hasMore: true
2 Last page /offset?offset=90&limit=10 Items 91–100, hasMore: false
3 Past the end /offset?offset=100&limit=10 200 with empty data, not a 404
4 Page size 0 /offset?limit=0 Clamped to 1 (or a 400, but decide)
5 Page size above max /offset?limit=101 Clamped to the max
6 Negative offset /offset?offset=-1 Treated as 0 or rejected
7 Non-numeric page /page?page=abc 400 naming the bad parameter
8 Last page flag /page?page=9&size=10 last: true, client stops
9 Tampered cursor /cursor?cursor=xyz 400 Invalid cursor, not a 500
10 Keyset after the last id /keyset?after_id=100 Empty data, next_after_id: null
11 Page 0 on a 1-based API /link?page=0&per_page=10 Treated as page 1
12 Walk every page fetch-all loop below Every item once, no gaps, no duplicates

The ones that catch real bugs:

#3, past the end. Some APIs return 404 here. Clients that treat 404 as "something broke" then retry forever. Returning an empty list is the friendlier contract.

#9, tampered cursor. Cursors are often base64 JSON. Decoding garbage throws, and if nobody catches it you return a 500 with a stack trace. Try it:

curl -i "https://api.testingapis.com/api/pagination/cursor?cursor=garbage"
# HTTP/1.1 400

#12, walk every page. This is the test I'd keep if I could only keep one:

const seen = new Set();
let cursor = null;
do {
  const url = new URL("https://api.testingapis.com/api/pagination/cursor");
  url.searchParams.set("limit", "10");
  if (cursor) url.searchParams.set("cursor", cursor);
  const page = await (await fetch(url)).json();
  for (const item of page.data) {
    if (seen.has(item.id)) throw new Error(`duplicate id ${item.id}`);
    seen.add(item.id);
  }
  cursor = page.next_cursor;
} while (cursor);
console.log(`${seen.size} items`); // expect 100

It catches off-by-one boundaries, a missing stop condition and duplicates in one go.

Two cases you can only test on your own data

These need data that changes between requests, so no public API can show them to you:

  • Delete a row on page 1 while a client is on page 2. Offset paging now skips an item. Cursor and keyset paging don't. If your feed uses offsets, this is a real bug, not a theory.
  • Sort by a column with duplicate values and no tie-breaker. ORDER BY created_at with two rows at the same timestamp can put the same row on two pages, or on neither. Always add the id as a tie-breaker.

Wrapping up

If your paging code passes all 12, it'll survive most real APIs. The full guide, with all five styles side by side and the responses for each case, is on testingapis.com. The endpoints are free and need no sign-up, so you can point your test suite at them.

What edge cases have bitten you? I'd like to add more to the list.

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.