-
-
Notifications
You must be signed in to change notification settings - Fork 34.8k
bpo-36871: Ensure method signature is used when asserting mock calls to a method #13261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
acfe960
4788924
5e9e4e6
ec2daf5
01aac71
67c9299
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…te was not accessed and add tests.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -743,7 +743,9 @@ def _get_call_signature_from_name(self, name): | |
| with tuples then return the self's spec signature. | ||
| If the name is not empty then remove () and split by '.' to get | ||
| list of names to iterate through the children until a potential | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. much clearer, thanks! |
||
| match is found. | ||
| match is found. A child mock is created only during attribute access | ||
| so if we get a _SpecState then no attributes of the spec were accessed | ||
| and can be safely exited. | ||
| """ | ||
| if not name: | ||
| return self._spec_signature | ||
|
|
@@ -754,13 +756,11 @@ def _get_call_signature_from_name(self, name): | |
|
|
||
| for name in names: | ||
| child = children.get(name) | ||
| if child is None: | ||
| if child is None or isinstance(child, _SpecState): | ||
| break | ||
| else: | ||
| children = child._mock_children.get(name) | ||
| children = child._mock_children | ||
| sig = child._spec_signature | ||
| if children is None: | ||
| break | ||
|
|
||
| return sig | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1339,31 +1339,36 @@ def test_assert_has_calls(self): | |||||||||||||||||||||||
| def test_assert_has_calls_nested_spec(self): | ||||||||||||||||||||||||
| class Something: | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def __init__(self, a): pass | ||||||||||||||||||||||||
| def __init__(self): pass | ||||||||||||||||||||||||
| def meth(self, a, b, c, d=None): pass | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| class Foo: | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def __init__(self): pass | ||||||||||||||||||||||||
| def meth1(self, a, b): pass | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| mock = create_autospec(Something) | ||||||||||||||||||||||||
| mock.meth(1, 2, 3, d=1) | ||||||||||||||||||||||||
| mock_class = create_autospec(Something) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| mock.assert_has_calls([call.meth(1, 2, 3, d=1)]) | ||||||||||||||||||||||||
| mock.assert_has_calls([call.meth(1, 2, 3, 1)]) | ||||||||||||||||||||||||
| for m in [mock_class, mock_class()]: | ||||||||||||||||||||||||
| m.meth(1, 2, 3, d=1) | ||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused: for the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I too had the same confusion :) It seems for both class and instance the signature skips self as in the below example. There is some note in docs at https://docs.python.org/3/library/unittest.mock.html#create-autospec but I am also little surprised at this.
from unittest.mock import *
import inspect
class A:
def meth1(self, a): pass
a = create_autospec(A)
a.meth1(1)
print(inspect.signature(a.meth1))
print(a.mock_calls)
# Use as an object
obj = a()
obj.meth1(1)
print(inspect.signature(obj.meth1))
print(obj.mock_calls)Similar test : cpython/Lib/unittest/test/testmock/testhelpers.py Lines 533 to 543 in 0f6f73f
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's unrelated to this PR, but this just feels like a bug to me. If you've autospecced from a class, the mock shouldn't have things that behave like classmethods if those things aren't classmethods on the object the spec is from. The "similar test" you've included is just as confusing: why should the mock have
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. one, two and three are methods of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah! That makes more sense :-) |
||||||||||||||||||||||||
| m.assert_has_calls([call.meth(1, 2, 3, d=1)]) | ||||||||||||||||||||||||
| m.assert_has_calls([call.meth(1, 2, 3, 1)]) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| mock = create_autospec(Something) | ||||||||||||||||||||||||
| mock.Foo().meth1(1, 2) | ||||||||||||||||||||||||
| mock_class = create_autospec(Something) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| mock.assert_has_calls([call.Foo(), call.Foo().meth1(1, 2)]) | ||||||||||||||||||||||||
| for m in [mock_class, mock_class()]: | ||||||||||||||||||||||||
| self.assertRaises(AssertionError, m.assert_has_calls, [call.Foo()]) | ||||||||||||||||||||||||
| m.Foo().meth1(1, 2) | ||||||||||||||||||||||||
| m.assert_has_calls([call.Foo(), call.Foo().meth1(1, 2)]) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| mock_class = create_autospec(Something) | ||||||||||||||||||||||||
| invalid_calls = [call.meth(1), | ||||||||||||||||||||||||
| call.non_existent(1), | ||||||||||||||||||||||||
| call.Foo().non_existent(1)] | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| for kall in invalid_calls: | ||||||||||||||||||||||||
| self.assertRaises(AssertionError, | ||||||||||||||||||||||||
| mock.assert_has_calls, | ||||||||||||||||||||||||
| mock_class.assert_has_calls, | ||||||||||||||||||||||||
| [kall] | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't really follow what this sentence is saying, could it be reworded?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have reworded the comment. Feel free to suggest if it can be rephrased better. An explanation using examples would be as below but I am not sure of using examples in the code comment.
obj.meth1.assert_has_calls([call(1)]herecall(1)object has no name to lookup and hence just returnobj.meth1._spec_signature.obj.assert_has_calls([call.meth1(1)])herecall.meth1(1)will have name 'meth1' that should be used to return signature ofobj.meth1unlike the bug whereobjsignature was returned.Thanks