Skip to content

Commit

Permalink
Add assert_has_calls_wrapper (#365)
Browse files Browse the repository at this point in the history
Fixes #234

---------

Co-authored-by: Astral <astralzhang@gmail.com>
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
  • Loading branch information
3 people committed Jun 15, 2023
1 parent 355b5aa commit 5818160
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 2 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.rst
Expand Up @@ -2,12 +2,16 @@ Releases
========

UNRELEASED
-------------------
----------

* Fixed introspection for failed ``assert_has_calls`` (`#365`_).

* Updated type annotations for ``mocker.patch`` and ``mocker.spy`` (`#364`_).

.. _#365: https://github.com/pytest-dev/pytest-mock/pull/365
.. _#364: https://github.com/pytest-dev/pytest-mock/pull/364


3.10.0 (2022-10-05)
-------------------

Expand Down
52 changes: 51 additions & 1 deletion src/pytest_mock/plugin.py
Expand Up @@ -473,6 +473,54 @@ def assert_wrapper(
raise e


def assert_has_calls_wrapper(
__wrapped_mock_method__: Callable[..., Any], *args: Any, **kwargs: Any
) -> None:
__tracebackhide__ = True
try:
__wrapped_mock_method__(*args, **kwargs)
return
except AssertionError as e:
any_order = kwargs.get("any_order", False)
if getattr(e, "_mock_introspection_applied", 0) or any_order:
msg = str(e)
else:
__mock_self = args[0]
msg = str(e)
if __mock_self.call_args_list is not None:
actual_calls = list(__mock_self.call_args_list)
expect_calls = args[1]
introspection = ""
from itertools import zip_longest

for actual_call, expect_call in zip_longest(actual_calls, expect_calls):
if actual_call is not None:
actual_args, actual_kwargs = actual_call
else:
actual_args = tuple()
actual_kwargs = {}

if expect_call is not None:
_, expect_args, expect_kwargs = expect_call
else:
expect_args = tuple()
expect_kwargs = {}

try:
assert actual_args == expect_args
except AssertionError as e_args:
introspection += "\nArgs:\n" + str(e_args)
try:
assert actual_kwargs == expect_kwargs
except AssertionError as e_kwargs:
introspection += "\nKwargs:\n" + str(e_kwargs)
if introspection:
msg += "\n\npytest introspection follows:\n" + introspection
e = AssertionError(msg)
e._mock_introspection_applied = True # type:ignore[attr-defined]
raise e


def wrap_assert_not_called(*args: Any, **kwargs: Any) -> None:
__tracebackhide__ = True
assert_wrapper(_mock_module_originals["assert_not_called"], *args, **kwargs)
Expand All @@ -495,7 +543,9 @@ def wrap_assert_called_once_with(*args: Any, **kwargs: Any) -> None:

def wrap_assert_has_calls(*args: Any, **kwargs: Any) -> None:
__tracebackhide__ = True
assert_wrapper(_mock_module_originals["assert_has_calls"], *args, **kwargs)
assert_has_calls_wrapper(
_mock_module_originals["assert_has_calls"], *args, **kwargs
)


def wrap_assert_any_call(*args: Any, **kwargs: Any) -> None:
Expand Down
80 changes: 80 additions & 0 deletions tests/test_pytest_mock.py
Expand Up @@ -630,6 +630,86 @@ def test_assert_has_calls(mocker: MockerFixture) -> None:
stub.assert_has_calls([mocker.call("bar")])


def test_assert_has_calls_multiple_calls(mocker: MockerFixture) -> None:
stub = mocker.stub()
stub("foo")
stub("bar")
stub("baz")
stub.assert_has_calls([mocker.call("foo"), mocker.call("bar"), mocker.call("baz")])
with assert_traceback():
stub.assert_has_calls(
[
mocker.call("foo"),
mocker.call("bar"),
mocker.call("baz"),
mocker.call("bat"),
]
)
with assert_traceback():
stub.assert_has_calls(
[mocker.call("foo"), mocker.call("baz"), mocker.call("bar")]
)


def test_assert_has_calls_multiple_calls_subset(mocker: MockerFixture) -> None:
stub = mocker.stub()
stub("foo")
stub("bar")
stub("baz")
stub.assert_has_calls([mocker.call("bar"), mocker.call("baz")])
with assert_traceback():
stub.assert_has_calls([mocker.call("foo"), mocker.call("baz")])
with assert_traceback():
stub.assert_has_calls(
[mocker.call("foo"), mocker.call("bar"), mocker.call("bat")]
)
with assert_traceback():
stub.assert_has_calls([mocker.call("baz"), mocker.call("bar")])


def test_assert_has_calls_multiple_calls_any_order(mocker: MockerFixture) -> None:
stub = mocker.stub()
stub("foo")
stub("bar")
stub("baz")
stub.assert_has_calls(
[mocker.call("foo"), mocker.call("baz"), mocker.call("bar")], any_order=True
)
with assert_traceback():
stub.assert_has_calls(
[
mocker.call("foo"),
mocker.call("baz"),
mocker.call("bar"),
mocker.call("bat"),
],
any_order=True,
)


def test_assert_has_calls_multiple_calls_any_order_subset(
mocker: MockerFixture,
) -> None:
stub = mocker.stub()
stub("foo")
stub("bar")
stub("baz")
stub.assert_has_calls([mocker.call("baz"), mocker.call("foo")], any_order=True)
with assert_traceback():
stub.assert_has_calls(
[mocker.call("baz"), mocker.call("foo"), mocker.call("bat")], any_order=True
)


def test_assert_has_calls_no_calls(
mocker: MockerFixture,
) -> None:
stub = mocker.stub()
stub.assert_has_calls([])
with assert_traceback():
stub.assert_has_calls([mocker.call("foo")])


def test_monkeypatch_ini(testdir: Any, mocker: MockerFixture) -> None:
# Make sure the following function actually tests something
stub = mocker.stub()
Expand Down

0 comments on commit 5818160

Please sign in to comment.