diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7b3fe1e..5973d65 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,11 @@ Releases ======== +UNRELEASED +---------- + +* `#641 `_: Failures of mock assertions such as ``assert_called_with`` are again reported only once, instead of two or three times with the original error from the ``mock`` module chained before them. + 3.16.1 ------ diff --git a/src/pytest_mock/plugin.py b/src/pytest_mock/plugin.py index 2bd0b8d..ece8610 100644 --- a/src/pytest_mock/plugin.py +++ b/src/pytest_mock/plugin.py @@ -565,7 +565,9 @@ def assert_wrapper( msg += "\n\npytest introspection follows:\n" + introspection e = AssertionError(msg) e._mock_introspection_applied = True # type:ignore[attr-defined] - raise e # noqa:TRY201 + # Suppress the context, otherwise pytest shows the original error + # (and the mock frames) chained before this one. + raise e from None def _unpack_expected_call(expect_call: Any) -> tuple[Any, Any] | None: @@ -651,7 +653,9 @@ def assert_has_calls_wrapper( msg += "\n\npytest introspection follows:\n" + introspection e = AssertionError(msg) e._mock_introspection_applied = True # type:ignore[attr-defined] - raise e # noqa:TRY201 + # Suppress the context, otherwise pytest shows the original error + # (and the mock frames) chained before this one. + raise e from None def wrap_assert_not_called(*args: Any, **kwargs: Any) -> None: diff --git a/tests/test_pytest_mock.py b/tests/test_pytest_mock.py index 984e816..89dc4cc 100644 --- a/tests/test_pytest_mock.py +++ b/tests/test_pytest_mock.py @@ -1106,6 +1106,35 @@ def test_foo(mocker): result = testdir.runpytest() assert "pytest introspection follows:" not in result.stdout.str() + @pytest.mark.parametrize( + "assertion", + [ + "stub.assert_called_with(2)", + # assert_called_once_with calls assert_called_with internally, + # so both wrappers are involved. + "stub.assert_called_once_with(2)", + "stub.assert_not_called()", + "stub.assert_has_calls([mocker.call(2)])", + ], + ) + def test_no_chained_tracebacks(self, testdir: Any, assertion: str) -> None: + """ + The wrapped assertions report the failure only once, without the + original exception from the mock module chained to it. + """ + testdir.makepyfile( + f""" + def test_foo(mocker): + stub = mocker.stub() + stub(1) + {assertion} + """ + ) + result = testdir.runpytest() + result.assert_outcomes(failed=1) + assert "During handling of the above exception" not in result.stdout.str() + assert "mock.py" not in result.stdout.str() + class TestAssertHasCalls: """``assert_has_calls``, including the shapes accepted as expected calls."""