Batu Lab NotesPractical developer guides

Sort a list in a test without mutating the expected fixture

By Batu · English technical notes

Also published in our primary archive.

To sort a list in a test without mutating the expected fixture, build the expected value with sorted from a separate input snapshot and assert the input’s state separately. Calling list.sort() on an expected fixture that aliases the input can create a false positive: the supposed oracle mutates the very object whose mutation should have failed the test.

The first case deliberately makes that mistake. expected = fixture creates an alias, and expected.sort() changes fixture to [1, 2, 3]. mutating_sort(fixture) then passes the equality assertion, but the assertion says nothing about whether the function was allowed to mutate its argument. The second case records bad_snapshot before calling the same mutating function and proves the input changed. The final case calls sorted_copy, compares its returned list with the sorted value, and proves that good_input still equals its snapshot.

Use list(values) when a one-level input snapshot is enough, and use sorted(values) when you need a new ordered result. These assertions cover list contents and list-level mutation only; they do not detect mutation inside nested mutable elements.

The built-in Python sorted documentation specifies that it returns a new sorted list, unlike the in-place list.sort() method.

AI assistance disclosure: This article was drafted with AI assistance and checked using the deterministic fixture below.

def mutating_sort(values):
    values.sort()
    return values


def sorted_copy(values):
    return sorted(values)


fixture = [3, 1, 2]
expected = fixture
expected.sort()
assert mutating_sort(fixture) == expected

bad_input = [3, 1, 2]
bad_snapshot = list(bad_input)
assert mutating_sort(bad_input) == [1, 2, 3]
assert bad_input != bad_snapshot

good_input = [3, 1, 2]
good_snapshot = list(good_input)
assert sorted_copy(good_input) == [1, 2, 3]
assert good_input == good_snapshot

print("aliased oracle passed: True")
print("mutating function changed input: True")
print("sorted-copy preserved input: True")
aliased oracle passed: True
mutating function changed input: True
sorted-copy preserved input: True