diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index c64587bb219..81799552f3f 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -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() } diff --git a/testing/test_approx_dict_keys_13816.py b/testing/test_approx_dict_keys_13816.py new file mode 100644 index 00000000000..e25433f8991 --- /dev/null +++ b/testing/test_approx_dict_keys_13816.py @@ -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