Repository navigation
Conversation
8fd5359 to
fe8eeab
Compare
nicoddemus
left a comment
There was a problem hiding this comment.
Thanks @muneikh! Showing the real arguments of child calls is a good idea.
First, a heads-up: #623 landed in main and conflicted with this PR, so I rebased your branch onto main and resolved the conflicts (fe8eeab). Please pull before pushing more changes. After the rebase, the wrapper takes the list to introspect as a parameter, and wrap_assert_has_calls now passes "mock_calls".
Unfortunately switching to mock_calls causes a problem for ordinary calls. mock_calls also records magic-method calls, and the introspection pairs actual and expected calls by position while ignoring the call name:
m = mocker.MagicMock()
with m:
m("a")
m.assert_has_calls([call("a"), call("b")])pytest introspection follows:
Args:
assert () == ('a',)
Args:
assert ('a',) == ('b',)
Args:
assert (None, None, None) == ()
call("a") gets paired with __enter__(), and __exit__'s arguments show up as a diff. On main this introspection only covers m(...) calls. Also, m.a("x"); m.assert_has_calls([call.b("y")]) shows only an 'x' vs 'y' diff, even though the real difference is the method name.
A possible direction: compare the call name as well as the arguments, and only show introspection for expected calls that don't match an actual call with the same name, instead of zipping both lists by index. Please also add tests that mix direct calls, child calls and magic methods (with m:), and check that the right calls are paired, not just that 'actual' and 'expected' appear somewhere in the output.
(Pairing by index was already imperfect before this PR, because assert_has_calls accepts the expected calls as a run anywhere in the list. You don't need to fix that here, but the approach above would handle it too.)
|
Thanks a lot for working on this, @muneikh, and for the thorough validation! We ended up going in a different direction: #643 removed the introspection from |
assert_has_calls checks mock_calls, which includes child mocks, but its failure introspection previously read only call_args_list. A child call such as mock.child("actual") therefore produced a misleading diff from an empty argument list rather than the actual child arguments.
Use the full mock_calls history and preserve call names when unpacking it. For failure introspection, reserve exact expected matches first, then compare remaining expectations only with unmatched actual calls of the same name. This prevents direct calls, child calls and context-manager magic methods from producing unrelated argument diffs, while retaining the original assertion message for missing names and order-only failures. Exact-match reservation honors ANY, overlapping custom matchers and autospec signatures. Add child-call and mixed-call regressions and a changelog note. The follow-up builds on the maintainer rebase after #623 and preserves its async-await introspection.
Validation: the original two child-call regressions fail before the initial change. Eleven of the thirteen additional pairing cases fail on the rebased pre-follow-up source, and all thirteen pass with the follow-up, including a standalone mock backend run. Full configured tox suites pass on Python 3.10.20 and 3.13.13 (125 tests each, with five existing pytest-asyncio deprecation warnings). All configured pre-commit checks pass: RST lint, Ruff lint and formatting, and Mypy; git diff --check passes. The full supported interpreter/OS matrix and hosted CI were not run.