Kaydet (Commit) 5c64df70 authored tarafından Antoine Pitrou's avatar Antoine Pitrou

Issue #17015: When it has a spec, a Mock object now inspects its signature when…

Issue #17015: When it has a spec, a Mock object now inspects its signature when matching calls, so that arguments can be matched positionally or by name.
üst 18b30ee8
......@@ -277,6 +277,20 @@ instantiate the class in those tests.
...
AttributeError: object has no attribute 'old_method'
Using a specification also enables a smarter matching of calls made to the
mock, regardless of whether some parameters were passed as positional or
named arguments::
>>> def f(a, b, c): pass
...
>>> mock = Mock(spec=f)
>>> mock(1, 2, 3)
<Mock name='mock()' id='140161580456576'>
>>> mock.assert_called_with(a=1, b=2, c=3)
If you want this smarter matching to also work with method calls on the mock,
you can use :ref:`auto-speccing <auto-speccing>`.
If you want a stronger form of specification that prevents the setting
of arbitrary attributes as well as the getting of them then you can use
`spec_set` instead of `spec`.
......
......@@ -264,7 +264,6 @@ the `new_callable` argument to `patch`.
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_with(1, 2, 3, test='wow')
.. method:: assert_called_once_with(*args, **kwargs)
Assert that the mock was called exactly once and with the specified
......@@ -685,6 +684,27 @@ have to create a dictionary and unpack it using `**`:
...
KeyError
A callable mock which was created with a *spec* (or a *spec_set*) will
introspect the specification object's signature when matching calls to
the mock. Therefore, it can match the actual call's arguments regardless
of whether they were passed positionally or by name::
>>> def f(a, b, c): pass
...
>>> mock = Mock(spec=f)
>>> mock(1, 2, c=3)
<Mock name='mock()' id='140161580456576'>
>>> mock.assert_called_with(1, 2, 3)
>>> mock.assert_called_with(a=1, b=2, c=3)
This applies to :meth:`~Mock.assert_called_with`,
:meth:`~Mock.assert_called_once_with`, :meth:`~Mock.assert_has_calls` and
:meth:`~Mock.assert_any_call`. When :ref:`auto-speccing`, it will also
apply to method calls on the mock object.
.. versionchanged:: 3.4
Added signature introspection on specced and autospecced mock objects.
.. class:: PropertyMock(*args, **kwargs)
......
This diff is collapsed.
......@@ -337,9 +337,10 @@ class SpecSignatureTest(unittest.TestCase):
def test_basic(self):
for spec in (SomeClass, SomeClass()):
mock = create_autospec(spec)
self._check_someclass_mock(mock)
mock = create_autospec(SomeClass)
self._check_someclass_mock(mock)
mock = create_autospec(SomeClass())
self._check_someclass_mock(mock)
def test_create_autospec_return_value(self):
......@@ -576,10 +577,10 @@ class SpecSignatureTest(unittest.TestCase):
def test_spec_inheritance_for_classes(self):
class Foo(object):
def a(self):
def a(self, x):
pass
class Bar(object):
def f(self):
def f(self, y):
pass
class_mock = create_autospec(Foo)
......@@ -587,26 +588,30 @@ class SpecSignatureTest(unittest.TestCase):
self.assertIsNot(class_mock, class_mock())
for this_mock in class_mock, class_mock():
this_mock.a()
this_mock.a.assert_called_with()
self.assertRaises(TypeError, this_mock.a, 'foo')
this_mock.a(x=5)
this_mock.a.assert_called_with(x=5)
this_mock.a.assert_called_with(5)
self.assertRaises(TypeError, this_mock.a, 'foo', 'bar')
self.assertRaises(AttributeError, getattr, this_mock, 'b')
instance_mock = create_autospec(Foo())
instance_mock.a()
instance_mock.a.assert_called_with()
self.assertRaises(TypeError, instance_mock.a, 'foo')
instance_mock.a(5)
instance_mock.a.assert_called_with(5)
instance_mock.a.assert_called_with(x=5)
self.assertRaises(TypeError, instance_mock.a, 'foo', 'bar')
self.assertRaises(AttributeError, getattr, instance_mock, 'b')
# The return value isn't isn't callable
self.assertRaises(TypeError, instance_mock)
instance_mock.Bar.f()
instance_mock.Bar.f.assert_called_with()
instance_mock.Bar.f(6)
instance_mock.Bar.f.assert_called_with(6)
instance_mock.Bar.f.assert_called_with(y=6)
self.assertRaises(AttributeError, getattr, instance_mock.Bar, 'g')
instance_mock.Bar().f()
instance_mock.Bar().f.assert_called_with()
instance_mock.Bar().f(6)
instance_mock.Bar().f.assert_called_with(6)
instance_mock.Bar().f.assert_called_with(y=6)
self.assertRaises(AttributeError, getattr, instance_mock.Bar(), 'g')
......@@ -663,12 +668,15 @@ class SpecSignatureTest(unittest.TestCase):
self.assertRaises(TypeError, mock)
mock(1, 2)
mock.assert_called_with(1, 2)
mock.assert_called_with(1, b=2)
mock.assert_called_with(a=1, b=2)
f.f = f
mock = create_autospec(f)
self.assertRaises(TypeError, mock.f)
mock.f(3, 4)
mock.f.assert_called_with(3, 4)
mock.f.assert_called_with(a=3, b=4)
def test_skip_attributeerrors(self):
......@@ -704,9 +712,13 @@ class SpecSignatureTest(unittest.TestCase):
self.assertRaises(TypeError, mock)
mock(1)
mock.assert_called_once_with(1)
mock.assert_called_once_with(a=1)
self.assertRaises(AssertionError, mock.assert_called_once_with, 2)
mock(4, 5)
mock.assert_called_with(4, 5)
mock.assert_called_with(a=4, b=5)
self.assertRaises(AssertionError, mock.assert_called_with, a=5, b=4)
def test_class_with_no_init(self):
......@@ -719,24 +731,27 @@ class SpecSignatureTest(unittest.TestCase):
def test_signature_callable(self):
class Callable(object):
def __init__(self):
def __init__(self, x, y):
pass
def __call__(self, a):
pass
mock = create_autospec(Callable)
mock()
mock.assert_called_once_with()
mock(1, 2)
mock.assert_called_once_with(1, 2)
mock.assert_called_once_with(x=1, y=2)
self.assertRaises(TypeError, mock, 'a')
instance = mock()
instance = mock(1, 2)
self.assertRaises(TypeError, instance)
instance(a='a')
instance.assert_called_once_with('a')
instance.assert_called_once_with(a='a')
instance('a')
instance.assert_called_with('a')
instance.assert_called_with(a='a')
mock = create_autospec(Callable())
mock = create_autospec(Callable(1, 2))
mock(a='a')
mock.assert_called_once_with(a='a')
self.assertRaises(TypeError, mock)
......@@ -779,7 +794,11 @@ class SpecSignatureTest(unittest.TestCase):
pass
a = create_autospec(Foo)
a.f(10)
a.f.assert_called_with(10)
a.f.assert_called_with(self=10)
a.f(self=10)
a.f.assert_called_with(10)
a.f.assert_called_with(self=10)
......
......@@ -25,6 +25,18 @@ class Iter(object):
__next__ = next
class Something(object):
def meth(self, a, b, c, d=None):
pass
@classmethod
def cmeth(cls, a, b, c, d=None):
pass
@staticmethod
def smeth(a, b, c, d=None):
pass
class MockTest(unittest.TestCase):
......@@ -273,6 +285,43 @@ class MockTest(unittest.TestCase):
mock.assert_called_with(1, 2, 3, a='fish', b='nothing')
def test_assert_called_with_function_spec(self):
def f(a, b, c, d=None):
pass
mock = Mock(spec=f)
mock(1, b=2, c=3)
mock.assert_called_with(1, 2, 3)
mock.assert_called_with(a=1, b=2, c=3)
self.assertRaises(AssertionError, mock.assert_called_with,
1, b=3, c=2)
# Expected call doesn't match the spec's signature
with self.assertRaises(AssertionError) as cm:
mock.assert_called_with(e=8)
self.assertIsInstance(cm.exception.__cause__, TypeError)
def test_assert_called_with_method_spec(self):
def _check(mock):
mock(1, b=2, c=3)
mock.assert_called_with(1, 2, 3)
mock.assert_called_with(a=1, b=2, c=3)
self.assertRaises(AssertionError, mock.assert_called_with,
1, b=3, c=2)
mock = Mock(spec=Something().meth)
_check(mock)
mock = Mock(spec=Something.cmeth)
_check(mock)
mock = Mock(spec=Something().cmeth)
_check(mock)
mock = Mock(spec=Something.smeth)
_check(mock)
mock = Mock(spec=Something().smeth)
_check(mock)
def test_assert_called_once_with(self):
mock = Mock()
mock()
......@@ -297,6 +346,29 @@ class MockTest(unittest.TestCase):
)
def test_assert_called_once_with_function_spec(self):
def f(a, b, c, d=None):
pass
mock = Mock(spec=f)
mock(1, b=2, c=3)
mock.assert_called_once_with(1, 2, 3)
mock.assert_called_once_with(a=1, b=2, c=3)
self.assertRaises(AssertionError, mock.assert_called_once_with,
1, b=3, c=2)
# Expected call doesn't match the spec's signature
with self.assertRaises(AssertionError) as cm:
mock.assert_called_once_with(e=8)
self.assertIsInstance(cm.exception.__cause__, TypeError)
# Mock called more than once => always fails
mock(4, 5, 6)
self.assertRaises(AssertionError, mock.assert_called_once_with,
1, 2, 3)
self.assertRaises(AssertionError, mock.assert_called_once_with,
4, 5, 6)
def test_attribute_access_returns_mocks(self):
mock = Mock()
something = mock.something
......@@ -991,6 +1063,39 @@ class MockTest(unittest.TestCase):
)
def test_assert_has_calls_with_function_spec(self):
def f(a, b, c, d=None):
pass
mock = Mock(spec=f)
mock(1, b=2, c=3)
mock(4, 5, c=6, d=7)
mock(10, 11, c=12)
calls = [
('', (1, 2, 3), {}),
('', (4, 5, 6), {'d': 7}),
((10, 11, 12), {}),
]
mock.assert_has_calls(calls)
mock.assert_has_calls(calls, any_order=True)
mock.assert_has_calls(calls[1:])
mock.assert_has_calls(calls[1:], any_order=True)
mock.assert_has_calls(calls[:-1])
mock.assert_has_calls(calls[:-1], any_order=True)
# Reversed order
calls = list(reversed(calls))
with self.assertRaises(AssertionError):
mock.assert_has_calls(calls)
mock.assert_has_calls(calls, any_order=True)
with self.assertRaises(AssertionError):
mock.assert_has_calls(calls[1:])
mock.assert_has_calls(calls[1:], any_order=True)
with self.assertRaises(AssertionError):
mock.assert_has_calls(calls[:-1])
mock.assert_has_calls(calls[:-1], any_order=True)
def test_assert_any_call(self):
mock = Mock()
mock(1, 2)
......@@ -1017,6 +1122,26 @@ class MockTest(unittest.TestCase):
)
def test_assert_any_call_with_function_spec(self):
def f(a, b, c, d=None):
pass
mock = Mock(spec=f)
mock(1, b=2, c=3)
mock(4, 5, c=6, d=7)
mock.assert_any_call(1, 2, 3)
mock.assert_any_call(a=1, b=2, c=3)
mock.assert_any_call(4, 5, 6, 7)
mock.assert_any_call(a=4, b=5, c=6, d=7)
self.assertRaises(AssertionError, mock.assert_any_call,
1, b=3, c=2)
# Expected call doesn't match the spec's signature
with self.assertRaises(AssertionError) as cm:
mock.assert_any_call(e=8)
self.assertIsInstance(cm.exception.__cause__, TypeError)
def test_mock_calls_create_autospec(self):
def f(a, b):
pass
......
......@@ -235,6 +235,10 @@ Core and Builtins
Library
-------
- Issue #17015: When it has a spec, a Mock object now inspects its signature
when matching calls, so that arguments can be matched positionally or
by name.
- Issue #15633: httplib.HTTPResponse is now mark closed when the server
sends less than the advertised Content-Length.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment