From d3193dab35e7861cd09728fc0c70d884d5b78e6a Mon Sep 17 00:00:00 2001 From: Charles-Meldhine Madi Mnemoi Date: Wed, 15 Oct 2025 14:51:36 +0200 Subject: [PATCH 1/4] fix(testing): `pytest.approx` returns a clearer error mesage when comparing mappings with different keys --- src/_pytest/python_api.py | 6 ++++++ testing/python/approx.py | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index c64587bb219..1e389eb0663 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -242,6 +242,12 @@ def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]: f"Lengths: {len(self.expected)} and {len(other_side)}", ] + if set(self.expected.keys()) != set(other_side.keys()): + return [ + "comparison failed.", + f"Mappings has different keys: expected {self.expected.keys()} but got {other_side.keys()}", + ] + approx_side_as_map = { k: self._approx_scalar(v) for k, v in self.expected.items() } diff --git a/testing/python/approx.py b/testing/python/approx.py index 9032e6c0cb4..251df723e12 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -11,6 +11,7 @@ import operator from operator import eq from operator import ne +import re from _pytest.pytester import Pytester from _pytest.python_api import _recursive_sequence_map @@ -1047,6 +1048,20 @@ def test_strange_sequence(self): assert b == pytest.approx(a, abs=2) assert b != pytest.approx(a, abs=0.5) + def test_approx_dicts_with_mismatch_on_keys(self) -> None: + """https://github.com/pytest-dev/pytest/issues/13816""" + expected = {"a": 1, "c": 3} + actual = {"a": 1, "b": 3} + + with pytest.raises( + AssertionError, + match=re.escape( + "comparison failed.\n Mappings has different keys: " + "expected dict_keys(['a', 'b']) but got dict_keys(['a', 'c'])" + ), + ): + assert expected == approx(actual) + class MyVec3: # incomplete """sequence like""" From 4ac631ff678212ab34606aac1c99a578f622026c Mon Sep 17 00:00:00 2001 From: Charles-Meldhine Madi Mnemoi Date: Wed, 15 Oct 2025 14:54:55 +0200 Subject: [PATCH 2/4] docs: add changelog entry --- changelog/13816.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/13816.bugfix.rst diff --git a/changelog/13816.bugfix.rst b/changelog/13816.bugfix.rst new file mode 100644 index 00000000000..432fc519ed5 --- /dev/null +++ b/changelog/13816.bugfix.rst @@ -0,0 +1 @@ +Fixed :func:`pytest.approx` which now returns a clearer error message when comparing mappings with different keys. From 8af256646f5901cd6608b611f7e4f2d66bb14003 Mon Sep 17 00:00:00 2001 From: Charles-Meldhine Madi Mnemoi Date: Wed, 15 Oct 2025 14:55:22 +0200 Subject: [PATCH 3/4] docs: add Charles-Meldhine Madi Mnemoi (cmnemoi) to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index b28f49776d6..5849228200b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -83,6 +83,7 @@ Carlos Jenkins Ceridwen Charles Cloud Charles Machalow +Charles-Meldhine Madi Mnemoi (cmnemoi) Charnjit SiNGH (CCSJ) Cheuk Ting Ho Chris Mahoney From 10c83c54dc4818a8d47878d7db3db1397e702ea4 Mon Sep 17 00:00:00 2001 From: Charles-Meldhine Madi Mnemoi Date: Wed, 15 Oct 2025 15:05:53 +0200 Subject: [PATCH 4/4] fix(testing): correct expected and actual dictionary keys --- testing/python/approx.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/python/approx.py b/testing/python/approx.py index 251df723e12..f870b9bd4d8 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -1050,8 +1050,8 @@ def test_strange_sequence(self): def test_approx_dicts_with_mismatch_on_keys(self) -> None: """https://github.com/pytest-dev/pytest/issues/13816""" - expected = {"a": 1, "c": 3} - actual = {"a": 1, "b": 3} + expected = {"a": 1, "b": 3} + actual = {"a": 1, "c": 3} with pytest.raises( AssertionError, @@ -1060,7 +1060,7 @@ def test_approx_dicts_with_mismatch_on_keys(self) -> None: "expected dict_keys(['a', 'b']) but got dict_keys(['a', 'c'])" ), ): - assert expected == approx(actual) + assert actual == approx(expected) class MyVec3: # incomplete