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. ...@@ -277,6 +277,20 @@ instantiate the class in those tests.
... ...
AttributeError: object has no attribute 'old_method' 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 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 of arbitrary attributes as well as the getting of them then you can use
`spec_set` instead of `spec`. `spec_set` instead of `spec`.
......
...@@ -264,7 +264,6 @@ the `new_callable` argument to `patch`. ...@@ -264,7 +264,6 @@ the `new_callable` argument to `patch`.
<Mock name='mock.method()' id='...'> <Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_with(1, 2, 3, test='wow') >>> mock.method.assert_called_with(1, 2, 3, test='wow')
.. method:: assert_called_once_with(*args, **kwargs) .. method:: assert_called_once_with(*args, **kwargs)
Assert that the mock was called exactly once and with the specified 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 `**`: ...@@ -685,6 +684,27 @@ have to create a dictionary and unpack it using `**`:
... ...
KeyError 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) .. class:: PropertyMock(*args, **kwargs)
......
This diff is collapsed.
...@@ -337,9 +337,10 @@ class SpecSignatureTest(unittest.TestCase): ...@@ -337,9 +337,10 @@ class SpecSignatureTest(unittest.TestCase):
def test_basic(self): def test_basic(self):
for spec in (SomeClass, SomeClass()): mock = create_autospec(SomeClass)
mock = create_autospec(spec) self._check_someclass_mock(mock)
self._check_someclass_mock(mock) mock = create_autospec(SomeClass())
self._check_someclass_mock(mock)
def test_create_autospec_return_value(self): def test_create_autospec_return_value(self):
...@@ -576,10 +577,10 @@ class SpecSignatureTest(unittest.TestCase): ...@@ -576,10 +577,10 @@ class SpecSignatureTest(unittest.TestCase):
def test_spec_inheritance_for_classes(self): def test_spec_inheritance_for_classes(self):
class Foo(object): class Foo(object):
def a(self): def a(self, x):
pass pass
class Bar(object): class Bar(object):
def f(self): def f(self, y):
pass pass
class_mock = create_autospec(Foo) class_mock = create_autospec(Foo)
...@@ -587,26 +588,30 @@ class SpecSignatureTest(unittest.TestCase): ...@@ -587,26 +588,30 @@ class SpecSignatureTest(unittest.TestCase):
self.assertIsNot(class_mock, class_mock()) self.assertIsNot(class_mock, class_mock())
for this_mock in class_mock, class_mock(): for this_mock in class_mock, class_mock():
this_mock.a() this_mock.a(x=5)
this_mock.a.assert_called_with() this_mock.a.assert_called_with(x=5)
self.assertRaises(TypeError, this_mock.a, 'foo') this_mock.a.assert_called_with(5)
self.assertRaises(TypeError, this_mock.a, 'foo', 'bar')
self.assertRaises(AttributeError, getattr, this_mock, 'b') self.assertRaises(AttributeError, getattr, this_mock, 'b')
instance_mock = create_autospec(Foo()) instance_mock = create_autospec(Foo())
instance_mock.a() instance_mock.a(5)
instance_mock.a.assert_called_with() instance_mock.a.assert_called_with(5)
self.assertRaises(TypeError, instance_mock.a, 'foo') instance_mock.a.assert_called_with(x=5)
self.assertRaises(TypeError, instance_mock.a, 'foo', 'bar')
self.assertRaises(AttributeError, getattr, instance_mock, 'b') self.assertRaises(AttributeError, getattr, instance_mock, 'b')
# The return value isn't isn't callable # The return value isn't isn't callable
self.assertRaises(TypeError, instance_mock) self.assertRaises(TypeError, instance_mock)
instance_mock.Bar.f() instance_mock.Bar.f(6)
instance_mock.Bar.f.assert_called_with() 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') self.assertRaises(AttributeError, getattr, instance_mock.Bar, 'g')
instance_mock.Bar().f() instance_mock.Bar().f(6)
instance_mock.Bar().f.assert_called_with() 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') self.assertRaises(AttributeError, getattr, instance_mock.Bar(), 'g')
...@@ -663,12 +668,15 @@ class SpecSignatureTest(unittest.TestCase): ...@@ -663,12 +668,15 @@ class SpecSignatureTest(unittest.TestCase):
self.assertRaises(TypeError, mock) self.assertRaises(TypeError, mock)
mock(1, 2) mock(1, 2)
mock.assert_called_with(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 f.f = f
mock = create_autospec(f) mock = create_autospec(f)
self.assertRaises(TypeError, mock.f) self.assertRaises(TypeError, mock.f)
mock.f(3, 4) mock.f(3, 4)
mock.f.assert_called_with(3, 4) mock.f.assert_called_with(3, 4)
mock.f.assert_called_with(a=3, b=4)
def test_skip_attributeerrors(self): def test_skip_attributeerrors(self):
...@@ -704,9 +712,13 @@ class SpecSignatureTest(unittest.TestCase): ...@@ -704,9 +712,13 @@ class SpecSignatureTest(unittest.TestCase):
self.assertRaises(TypeError, mock) self.assertRaises(TypeError, mock)
mock(1) mock(1)
mock.assert_called_once_with(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(4, 5)
mock.assert_called_with(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): def test_class_with_no_init(self):
...@@ -719,24 +731,27 @@ class SpecSignatureTest(unittest.TestCase): ...@@ -719,24 +731,27 @@ class SpecSignatureTest(unittest.TestCase):
def test_signature_callable(self): def test_signature_callable(self):
class Callable(object): class Callable(object):
def __init__(self): def __init__(self, x, y):
pass pass
def __call__(self, a): def __call__(self, a):
pass pass
mock = create_autospec(Callable) mock = create_autospec(Callable)
mock() mock(1, 2)
mock.assert_called_once_with() mock.assert_called_once_with(1, 2)
mock.assert_called_once_with(x=1, y=2)
self.assertRaises(TypeError, mock, 'a') self.assertRaises(TypeError, mock, 'a')
instance = mock() instance = mock(1, 2)
self.assertRaises(TypeError, instance) self.assertRaises(TypeError, instance)
instance(a='a') instance(a='a')
instance.assert_called_once_with('a')
instance.assert_called_once_with(a='a') instance.assert_called_once_with(a='a')
instance('a') instance('a')
instance.assert_called_with('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(a='a')
mock.assert_called_once_with(a='a') mock.assert_called_once_with(a='a')
self.assertRaises(TypeError, mock) self.assertRaises(TypeError, mock)
...@@ -779,7 +794,11 @@ class SpecSignatureTest(unittest.TestCase): ...@@ -779,7 +794,11 @@ class SpecSignatureTest(unittest.TestCase):
pass pass
a = create_autospec(Foo) 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(self=10)
a.f.assert_called_with(10)
a.f.assert_called_with(self=10) a.f.assert_called_with(self=10)
......
...@@ -25,6 +25,18 @@ class Iter(object): ...@@ -25,6 +25,18 @@ class Iter(object):
__next__ = next __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): class MockTest(unittest.TestCase):
...@@ -273,6 +285,43 @@ class MockTest(unittest.TestCase): ...@@ -273,6 +285,43 @@ class MockTest(unittest.TestCase):
mock.assert_called_with(1, 2, 3, a='fish', b='nothing') 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): def test_assert_called_once_with(self):
mock = Mock() mock = Mock()
mock() mock()
...@@ -297,6 +346,29 @@ class MockTest(unittest.TestCase): ...@@ -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): def test_attribute_access_returns_mocks(self):
mock = Mock() mock = Mock()
something = mock.something something = mock.something
...@@ -991,6 +1063,39 @@ class MockTest(unittest.TestCase): ...@@ -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): def test_assert_any_call(self):
mock = Mock() mock = Mock()
mock(1, 2) mock(1, 2)
...@@ -1017,6 +1122,26 @@ class MockTest(unittest.TestCase): ...@@ -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 test_mock_calls_create_autospec(self):
def f(a, b): def f(a, b):
pass pass
......
...@@ -235,6 +235,10 @@ Core and Builtins ...@@ -235,6 +235,10 @@ Core and Builtins
Library 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 - Issue #15633: httplib.HTTPResponse is now mark closed when the server
sends less than the advertised Content-Length. 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