Quick answer: If the API does not guarantee an order, your test should not assert one. Comparing a returned array element-by-element against a fixed sequence makes the test fail whenever the database changes its physical read order, which can happen without any real defect. Assert the contents as a set, and only compare order when the contract explicitly defines it, such as a sorted query or a cursor feed that promises a sequence.

Why the order changes without a bug

Relational databases only return rows in a defined order when the query includes an explicit ORDER BY. Without it, the physical access plan decides the sequence, and that plan can shift when an index is added, a parallel scan is chosen, or the table grows and the optimizer changes strategy. A test that hardcodes the resulting sequence is therefore measuring an implementation detail, not a contract. The same data can come back in a different order and still be perfectly correct.

Assertion styleWhat it checksFailure risk
Exact ordered arrayContent and positionBrittle when order is unspecified
Set or multiset comparisonContent regardless of orderStable across plan changes
Order only on a sorted queryAn explicit contractCorrect and deterministic
Sorted-before-compareContent with a chosen keyRobust and readable

A brittle test and a stable one

Here is the fragile version. It assumes the list endpoint returns the two records in a fixed order even though the operation documents no sorting.

// Brittle: position is asserted but never promised
const res = await client.get("/projects");
expect(res.body).toEqual([
  { id: "p-1", name: "Atlas" },
  { id: "p-2", name: "Beacon" }
]);

If the database returns the same two rows reversed, the test fails despite the response being correct. The stable version asserts the contents independent of position.

const res = await client.get("/projects");
const byId = new Map(res.body.map((p) => [p.id, p]));
expect(byId.has("p-1")).toBe(true);
expect(byId.has("p-2")).toBe(true);
expect(res.body).toHaveLength(2);

Alternatively, sort both the expected and actual arrays by a stable key before comparing. That keeps a concise deep-equality check while removing the dependency on an unspecified order.

When order is part of the contract

Some operations genuinely promise a sequence. A leaderboard sorted by score, a feed that walks a cursor, or a query that accepts a sort parameter should be tested for order, because the client may rely on it. The key is to pin the test to the documented rule rather than to an accidental database behavior. Assert that each adjacent pair satisfies the ordering, and include the sort parameter explicitly so the test is self-contained.

const res = await client.get("/projects?sort=createdAt");
const dates = res.body.map((p) => p.createdAt);
const sorted = [...dates].sort();
expect(dates).toEqual(sorted);

Duplicates and multiset semantics

A plain set comparison silently drops duplicates, which can hide a real defect if the same record appears twice. When duplicates matter, use a multiset comparison that counts occurrences, or sort by a unique key and compare the full objects. Choosing the right comparison is itself a design decision: ask whether the API promises uniqueness for that collection before deciding that a set is sufficient.

How an agent makes it worse

An agent that sees an order-related failure often adds a sort inside the test or hardcodes the new sequence, which makes the test pass today but stays brittle. A better instruction asks the agent to determine whether the operation guarantees order and, if it does not, replace the positional assertion with a content-based one. If the agent instead proposes adding an ORDER BY to production code just to satisfy a test, question that: changing the contract to calm a test is backwards unless the ordered behavior is genuinely wanted.

Documenting the promise

When order does matter, write it into the OpenAPI description and name the default sort key, so clients and tests share one source of truth. An endpoint that is silent about ordering leaves every consumer to assume differently, and the test suite ends up enforcing whichever assumption the author happened to write. An explicit statement turns a flaky positional check into a verified contract.

Building a reusable comparison helper

When the same content-versus-order question appears across many tests, a small shared helper removes the temptation to copy a brittle assertion. A helper that compares two collections as a multiset by key keeps the intent explicit and gives every test the same robust behavior, so the ordering rule is decided once rather than re-litigated in each file.

function expectSameRecords(actual, expected, key) {
  const toMap = (rows) =>
    new Map(rows.map((row) => [row[key], row]));
  const a = toMap(actual);
  const b = toMap(expected);
  expect(a.size).toBe(b.size);
  for (const [id, row] of b) {
    expect(a.get(id)).toEqual(row);
  }
}

// Content is asserted without depending on position
expectSameRecords(res.body, expectedProjects, "id");

If the collection genuinely allows duplicates, extend the helper to count occurrences rather than keying them away, so the check reflects the real contract. The point is to make the choice of semantics deliberate and visible in one place, which keeps the suite stable as the database access plans change underneath.

Frequently asked questions

Can I just sort the response before asserting?

Yes, when you only care about content. Sorting both expected and actual arrays by a stable key gives a robust deep comparison without depending on the server's order.

What if the API does promise an order?

Then assert the order deliberately, ideally by checking adjacent elements against the documented sort rule, and include the sort parameter so the test is deterministic.

Does a set comparison hide duplicate records?

It can. If duplicates are meaningful, use a multiset or a sorted full-object comparison that preserves counts rather than collapsing identical items.

Why does the order change between runs?

Without an explicit ORDER BY, the database access plan decides the order, and index or optimizer changes can reorder rows even though the data is unchanged.

This is part of the The API Testing Guide — start there for more patterns that keep API tests meaningful instead of brittle.