Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/_pytest/python_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,19 @@ def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]:
f"Lengths: {len(self.expected)} and {len(other_side)}",
]

# Check if keys match
expected_keys = set(self.expected.keys())
other_keys = set(other_side.keys())
if expected_keys != other_keys:
expected_only = expected_keys - other_keys
other_only = other_keys - expected_keys
msg = ["Dictionaries have different keys."]
if expected_only or other_only:
msg.append(
f"Expected keys: {sorted(expected_keys)}, Actual keys: {sorted(other_keys)}"
)
return msg

approx_side_as_map = {
k: self._approx_scalar(v) for k, v in self.expected.items()
}
Expand Down
54 changes: 54 additions & 0 deletions testing/test_approx_dict_keys_13816.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Test for issue #13816 - better error messages for dict key mismatches in pytest.approx()"""

from __future__ import annotations

import pytest


def test_approx_dicts_with_different_keys_clear_error():
"""Test that pytest.approx() gives a clear error when dict keys don't match."""
expected = {"a": 1, "c": 3}
actual = {"a": 1, "b": 4}

with pytest.raises(AssertionError) as exc_info:
assert pytest.approx(actual) == expected

error_message = str(exc_info.value)
# Should mention "different keys" clearly
assert "different keys" in error_message.lower()
# Should NOT have the confusing KeyError message
assert "KeyError" not in error_message
# Should NOT mention "faulty __repr__"
assert "faulty __repr__" not in error_message


def test_approx_dicts_with_extra_key_in_expected():
"""Test error message when expected has extra keys."""
expected = {"a": 1, "b": 2, "c": 3}
actual = {"a": 1, "b": 2}

with pytest.raises(AssertionError) as exc_info:
assert pytest.approx(actual) == expected

error_message = str(exc_info.value)
assert "different keys" in error_message.lower()


def test_approx_dicts_with_extra_key_in_actual():
"""Test error message when actual has extra keys."""
expected = {"a": 1, "b": 2}
actual = {"a": 1, "b": 2, "c": 3}

with pytest.raises(AssertionError) as exc_info:
assert pytest.approx(actual) == expected

error_message = str(exc_info.value)
assert "different keys" in error_message.lower()


def test_approx_dicts_matching_keys_still_works():
"""Test that dicts with matching keys still work normally."""
expected = {"a": 1.0001, "b": 2.0001}
actual = {"a": 1.0, "b": 2.0}

assert pytest.approx(actual, rel=1e-3) == expected
Loading