bpo-36871: Ensure method signature is used when asserting mock calls to a method (GH15578)
* Fix call_matcher for mock when using methods
* Add NEWS entry
* Use None check and convert doctest to unittest
* Use better name for mock in tests. Handle _SpecState when the attribute was not accessed and add tests.
* Use reset_mock instead of reinitialization. Change inner class constructor signature for check
* Reword comment regarding call object lookup logic
(cherry picked from commit c96127821e
)
Co-authored-by: Xtreak <tir.karthi@gmail.com>
This commit is contained in:
parent
612d3935ec
commit
38d311d79e
|
@ -804,6 +804,35 @@ class NonCallableMock(Base):
|
|||
return message % (action, expected_string, actual_string)
|
||||
|
||||
|
||||
def _get_call_signature_from_name(self, name):
|
||||
"""
|
||||
* If call objects are asserted against a method/function like obj.meth1
|
||||
then there could be no name for the call object to lookup. Hence just
|
||||
return the spec_signature of the method/function being asserted against.
|
||||
* If the name is not empty then remove () and split by '.' to get
|
||||
list of names to iterate through the children until a potential
|
||||
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
|
||||
|
||||
sig = None
|
||||
names = name.replace('()', '').split('.')
|
||||
children = self._mock_children
|
||||
|
||||
for name in names:
|
||||
child = children.get(name)
|
||||
if child is None or isinstance(child, _SpecState):
|
||||
break
|
||||
else:
|
||||
children = child._mock_children
|
||||
sig = child._spec_signature
|
||||
|
||||
return sig
|
||||
|
||||
|
||||
def _call_matcher(self, _call):
|
||||
"""
|
||||
Given a call (or simply an (args, kwargs) tuple), return a
|
||||
|
@ -811,7 +840,12 @@ class NonCallableMock(Base):
|
|||
This is a best effort method which relies on the spec's signature,
|
||||
if available, or falls back on the arguments themselves.
|
||||
"""
|
||||
sig = self._spec_signature
|
||||
|
||||
if isinstance(_call, tuple) and len(_call) > 2:
|
||||
sig = self._get_call_signature_from_name(_call[0])
|
||||
else:
|
||||
sig = self._spec_signature
|
||||
|
||||
if sig is not None:
|
||||
if len(_call) == 2:
|
||||
name = ''
|
||||
|
|
|
@ -1339,6 +1339,54 @@ class MockTest(unittest.TestCase):
|
|||
)
|
||||
|
||||
|
||||
def test_assert_has_calls_nested_spec(self):
|
||||
class Something:
|
||||
|
||||
def __init__(self): pass
|
||||
def meth(self, a, b, c, d=None): pass
|
||||
|
||||
class Foo:
|
||||
|
||||
def __init__(self, a): pass
|
||||
def meth1(self, a, b): pass
|
||||
|
||||
mock_class = create_autospec(Something)
|
||||
|
||||
for m in [mock_class, mock_class()]:
|
||||
m.meth(1, 2, 3, d=1)
|
||||
m.assert_has_calls([call.meth(1, 2, 3, d=1)])
|
||||
m.assert_has_calls([call.meth(1, 2, 3, 1)])
|
||||
|
||||
mock_class.reset_mock()
|
||||
|
||||
for m in [mock_class, mock_class()]:
|
||||
self.assertRaises(AssertionError, m.assert_has_calls, [call.Foo()])
|
||||
m.Foo(1).meth1(1, 2)
|
||||
m.assert_has_calls([call.Foo(1), call.Foo(1).meth1(1, 2)])
|
||||
m.Foo.assert_has_calls([call(1), call().meth1(1, 2)])
|
||||
|
||||
mock_class.reset_mock()
|
||||
|
||||
invalid_calls = [call.meth(1),
|
||||
call.non_existent(1),
|
||||
call.Foo().non_existent(1),
|
||||
call.Foo().meth(1, 2, 3, 4)]
|
||||
|
||||
for kall in invalid_calls:
|
||||
self.assertRaises(AssertionError,
|
||||
mock_class.assert_has_calls,
|
||||
[kall]
|
||||
)
|
||||
|
||||
|
||||
def test_assert_has_calls_nested_without_spec(self):
|
||||
m = MagicMock()
|
||||
m().foo().bar().baz()
|
||||
m.one().two().three()
|
||||
calls = call.one().two().three().call_list()
|
||||
m.assert_has_calls(calls)
|
||||
|
||||
|
||||
def test_assert_has_calls_with_function_spec(self):
|
||||
def f(a, b, c, d=None): pass
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
Ensure method signature is used instead of constructor signature of a class
|
||||
while asserting mock object against method calls. Patch by Karthikeyan
|
||||
Singaravelan.
|
Loading…
Reference in New Issue