Skip to content

Commit 5818160

Browse files
acovaciAstral1020nicoddemus
authored
Add assert_has_calls_wrapper (#365)
Fixes #234 --------- Co-authored-by: Astral <[email protected]> Co-authored-by: Bruno Oliveira <[email protected]>
1 parent 355b5aa commit 5818160

File tree

3 files changed

+136
-2
lines changed

3 files changed

+136
-2
lines changed

CHANGELOG.rst

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ Releases
22
========
33

44
UNRELEASED
5-
-------------------
5+
----------
6+
7+
* Fixed introspection for failed ``assert_has_calls`` (`#365`_).
68

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

11+
.. _#365: https://github.com/pytest-dev/pytest-mock/pull/365
912
.. _#364: https://github.com/pytest-dev/pytest-mock/pull/364
1013

14+
1115
3.10.0 (2022-10-05)
1216
-------------------
1317

src/pytest_mock/plugin.py

+51-1
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,54 @@ def assert_wrapper(
473473
raise e
474474

475475

476+
def assert_has_calls_wrapper(
477+
__wrapped_mock_method__: Callable[..., Any], *args: Any, **kwargs: Any
478+
) -> None:
479+
__tracebackhide__ = True
480+
try:
481+
__wrapped_mock_method__(*args, **kwargs)
482+
return
483+
except AssertionError as e:
484+
any_order = kwargs.get("any_order", False)
485+
if getattr(e, "_mock_introspection_applied", 0) or any_order:
486+
msg = str(e)
487+
else:
488+
__mock_self = args[0]
489+
msg = str(e)
490+
if __mock_self.call_args_list is not None:
491+
actual_calls = list(__mock_self.call_args_list)
492+
expect_calls = args[1]
493+
introspection = ""
494+
from itertools import zip_longest
495+
496+
for actual_call, expect_call in zip_longest(actual_calls, expect_calls):
497+
if actual_call is not None:
498+
actual_args, actual_kwargs = actual_call
499+
else:
500+
actual_args = tuple()
501+
actual_kwargs = {}
502+
503+
if expect_call is not None:
504+
_, expect_args, expect_kwargs = expect_call
505+
else:
506+
expect_args = tuple()
507+
expect_kwargs = {}
508+
509+
try:
510+
assert actual_args == expect_args
511+
except AssertionError as e_args:
512+
introspection += "\nArgs:\n" + str(e_args)
513+
try:
514+
assert actual_kwargs == expect_kwargs
515+
except AssertionError as e_kwargs:
516+
introspection += "\nKwargs:\n" + str(e_kwargs)
517+
if introspection:
518+
msg += "\n\npytest introspection follows:\n" + introspection
519+
e = AssertionError(msg)
520+
e._mock_introspection_applied = True # type:ignore[attr-defined]
521+
raise e
522+
523+
476524
def wrap_assert_not_called(*args: Any, **kwargs: Any) -> None:
477525
__tracebackhide__ = True
478526
assert_wrapper(_mock_module_originals["assert_not_called"], *args, **kwargs)
@@ -495,7 +543,9 @@ def wrap_assert_called_once_with(*args: Any, **kwargs: Any) -> None:
495543

496544
def wrap_assert_has_calls(*args: Any, **kwargs: Any) -> None:
497545
__tracebackhide__ = True
498-
assert_wrapper(_mock_module_originals["assert_has_calls"], *args, **kwargs)
546+
assert_has_calls_wrapper(
547+
_mock_module_originals["assert_has_calls"], *args, **kwargs
548+
)
499549

500550

501551
def wrap_assert_any_call(*args: Any, **kwargs: Any) -> None:

tests/test_pytest_mock.py

+80
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,86 @@ def test_assert_has_calls(mocker: MockerFixture) -> None:
630630
stub.assert_has_calls([mocker.call("bar")])
631631

632632

633+
def test_assert_has_calls_multiple_calls(mocker: MockerFixture) -> None:
634+
stub = mocker.stub()
635+
stub("foo")
636+
stub("bar")
637+
stub("baz")
638+
stub.assert_has_calls([mocker.call("foo"), mocker.call("bar"), mocker.call("baz")])
639+
with assert_traceback():
640+
stub.assert_has_calls(
641+
[
642+
mocker.call("foo"),
643+
mocker.call("bar"),
644+
mocker.call("baz"),
645+
mocker.call("bat"),
646+
]
647+
)
648+
with assert_traceback():
649+
stub.assert_has_calls(
650+
[mocker.call("foo"), mocker.call("baz"), mocker.call("bar")]
651+
)
652+
653+
654+
def test_assert_has_calls_multiple_calls_subset(mocker: MockerFixture) -> None:
655+
stub = mocker.stub()
656+
stub("foo")
657+
stub("bar")
658+
stub("baz")
659+
stub.assert_has_calls([mocker.call("bar"), mocker.call("baz")])
660+
with assert_traceback():
661+
stub.assert_has_calls([mocker.call("foo"), mocker.call("baz")])
662+
with assert_traceback():
663+
stub.assert_has_calls(
664+
[mocker.call("foo"), mocker.call("bar"), mocker.call("bat")]
665+
)
666+
with assert_traceback():
667+
stub.assert_has_calls([mocker.call("baz"), mocker.call("bar")])
668+
669+
670+
def test_assert_has_calls_multiple_calls_any_order(mocker: MockerFixture) -> None:
671+
stub = mocker.stub()
672+
stub("foo")
673+
stub("bar")
674+
stub("baz")
675+
stub.assert_has_calls(
676+
[mocker.call("foo"), mocker.call("baz"), mocker.call("bar")], any_order=True
677+
)
678+
with assert_traceback():
679+
stub.assert_has_calls(
680+
[
681+
mocker.call("foo"),
682+
mocker.call("baz"),
683+
mocker.call("bar"),
684+
mocker.call("bat"),
685+
],
686+
any_order=True,
687+
)
688+
689+
690+
def test_assert_has_calls_multiple_calls_any_order_subset(
691+
mocker: MockerFixture,
692+
) -> None:
693+
stub = mocker.stub()
694+
stub("foo")
695+
stub("bar")
696+
stub("baz")
697+
stub.assert_has_calls([mocker.call("baz"), mocker.call("foo")], any_order=True)
698+
with assert_traceback():
699+
stub.assert_has_calls(
700+
[mocker.call("baz"), mocker.call("foo"), mocker.call("bat")], any_order=True
701+
)
702+
703+
704+
def test_assert_has_calls_no_calls(
705+
mocker: MockerFixture,
706+
) -> None:
707+
stub = mocker.stub()
708+
stub.assert_has_calls([])
709+
with assert_traceback():
710+
stub.assert_has_calls([mocker.call("foo")])
711+
712+
633713
def test_monkeypatch_ini(testdir: Any, mocker: MockerFixture) -> None:
634714
# Make sure the following function actually tests something
635715
stub = mocker.stub()

0 commit comments

Comments
 (0)